Race Condition nedir ve nasıl önlenir?
Bu flood’da çok thread’li uygulamalarda karşılaşılan Race Condition
probleminin ne olduğunu ve hangi yöntemlerle önlenebileceğini anlatacağım.
Race Condition
birden çok thread'in paylaşılan bir hafıza alanına aynı anda ulaşıp o alanı değiştirmesiyle meydana gelir. Race Condition
'lar genellikle uygulamanın doğru çalışmasını bozdukları için bug
olarak adlandırılırlar.
En basit ve ağır şekli ile Race Condition
'ın nasıl oluştuğunu anlamak için aşağıdaki iki thread'in bir sayacı 5M
kere artırdığı ancak sayacın 2 * 5M
= 10M
yerine ~7.2M
olduğu durumu ele alalım.
Kodlardan da görüldüğü üzere iki thread eş zamanlı olarak başlatılarak sürekli olarak counter
adlı değişkeni artırmaya çalışmışlardır. Thread'lerin counter
'u artırmaktan başka bir iş yapmaması sebebiyle Race Condition
en ağır şekliyle görülmektedir.
Şimdi toplam sayının 10M
yerine neden ~7.2M
olduğuna, yani Race Condition
'ın nasıl oluştuğuna bir seviye aşağı inerek daha yakından bakalım.
Yukarıdaki detaylandırılan işlemlerden görüldüğü üzere counter = counter + 1
işlemini gerçekleştirmek için işlemci register'larını da içeren bir dizi işlemler yapılmaktadır. Bu işlemler bir bütün olarak gerçekleştirilemediğinden Race Condition
'lar ortaya çıkmaktadır.
Artık Race Condition
'ların nasıl oluştuğunu anladığımıza göre Race Condition
'lardan nasıl kaçınabileceğimize bakabiliriz. Önceki flood'ların birinde https://medium.com/@gokhansengun/ba552a17c03 hatırlayacağınız üzere Critical Section
'lardan bahsetmiştik.
Paylaşılan hafıza alanına erişim yapılan kodlar Critical Section
içine alınırsa aynı kod bloğu aynı anda sadece bir thread tarafından çalıştırılır ve dolayısıyla Race Condition
oluşması engellenmiş olur.
Aşağıda bir önceki örnekte Race Condition
oluşturan sayaç artırma örneğinde sayaç artıran kod bloğu Critical Section
içerisine alınarak Race Condition
oluşumunun engellendiği gösterilmiştir.
Critical Section
'ların Race Condition
'ı engelleme maliyeti tahmin edilebileceği gibi performanstır. Aşağıdaki şekilden de görülebileceği üzere Critical Section
'ın dahil olması ile işlem süresi yaklaşık üç katına çıkmıştır.
Performansın kritik olduğu sistemlerde Critical Section
kullanımını minimize edecek önlemlere başvurulmalıdır. Örneğin bu tip sistemlerde uygulama yapısını değiştirerek Critical Section
'lar azaltılabilir ve Lock-free
veri yapılarını tercih etmek gerekebilir.
Performansın çok kritik olmadığı veya ortak kaynaklara erişimin fazla olmadığı sistemlerde ise kod kompleksliğini fazla artırmamak adına Critical Section
kullanarak Race Condition
'lardan korunmak ve kodu sade bırakmak iyi bir fikirdir.
Yukarıda gösterildiği üzere Race Condition
'ları önlemek üzere sürekli olarak Critical Section
'lara girilmesi performansı olumsuz olarak etkilemektedir. Double-checked Locking
yöntemi ile bazı kullanım senaryoları için Critical Section
maliyeti azaltılabilir.
Örnek olarak birçok thread tarafından ortak kullanılacak Singleton
bir objenin oluşturulması için bir kod bloğu yazdığımızı düşünelim. Burada biri Critical Section
içinde bulunmak üzere iç içe iki if kullanılarak objeyi sadece ilk thread'in yaratmasını sağlayabiliriz.
Aşağıdaki ekran görüntüsünde Double-checked Locking
bir Python kodu ile örneklenmiştir. Görüleceği üzere Critical Section
'a ilk ulaşan thread bir kereliğine objeyi oluşturmuş diğerleri ise oluşturulan bu objeyi kullanımış yeni objeler oluşturmamıştır.
Popüler işlemciler bir değişkenin Atomic
olarak yani başka thread tarafından bölünemez bir şekilde değiştirilmesine olanak tanırlar. Ortak kullanılan değişkenlerin kullanılan dilin sunduğu Atomic
kütüphaneler vasıtasıyla erişilmesiyle de Race Condition
'dan kaçınılabilir.
Bazı Race Condition
'lar çok uzun sürede ancak tekrarlanabildiği için haklarında bilgi toplanması ve çözülmesi zor problemler olarak karşımıza çıkarlar. Bu nedenle Race Condition
'dan kaçınmanın en iyi yolu bilinç düzeyini artırmak ve kod gözden geçirmelerini sıkılaştırmaktır.