Concurrency: Race Condition ve Deadlock

Eşzamanlı işlemlerde paylaşılan veriyi korumayı; atomicity, lock, deadlock ve mesajlaşma alternatifleriyle öğrenin.

Stokta bir ürün kaldığında iki istek aynı anda satın alma yaparsa ne olur? İkisi de stoğu 1 okuyup 0 yazabilir ve iki sipariş oluşur. Sonucun işlemlerin zamanlamasına bağlı olmasına race condition denir.

Terimler

Concurrency, birden fazla işin ilerlemesidir; aynı anda fiziksel çalışma şart değildir. Parallelism, işlerin gerçekten aynı anda farklı çekirdeklerde çalışmasıdır. Critical section, paylaşılan veriye erişen ve birlikte korunması gereken kod bölümüdür. Atomic operation, dışarıdan bölünemez tek işlem gibi görünür.

mutex.lock();
try {
  if (stock > 0) stock -= 1;
} finally {
  mutex.unlock();
}

Mutex (mutual exclusion), aynı kritik bölgeye aynı anda yalnızca bir yürütme akışının girmesini sağlayan kilittir. lock, mutex müsait değilse başka thread’i bekletir. try/finally, hata oluşsa bile unlock çalışmasını garanti eder. Fakat uygulama seviyesindeki mutex yalnızca aynı process’i korur; birden fazla sunucuya dağıtılmış sistemde tek başına yeterli değildir.

Deadlock nasıl oluşur?

Thread A kilit 1’i, Thread B kilit 2’yi alır; sonra ikisi de diğer kilidi beklerse ilerleme durur. Kaynakları her zaman aynı sırada kilitlemek, timeout kullanmak ve kritik bölgeyi kısa tutmak riski azaltır.

Alternatif çözümler

  • Optimistic concurrency: Kaydın version değerini kontrol eder; çakışmada işlem başarısız olur ve sınırlı sayıda yeniden denenir. Bu yeniden denemeye retry denir.
  • Database atomic update: UPDATE products SET stock = stock - 1 WHERE id = ? AND stock > 0 kontrol ile değişikliği tek komutta yapar.
  • Message passing: Paylaşılan belleği azaltır; tek consumer ilgili anahtarın işlemlerini sırayla uygulayabilir.
  • Immutable data: Yerinde değişiklik yerine yeni değer üretir, paylaşımı daha güvenli hâle getirir.

Lock her zaman ilk çözüm değildir. Çakışma azsa optimistic yaklaşım, çoksa işlemi önceden kilitleyen pessimistic lock, doğal sıralama varsa kuyruk uygun olabilir. Retry yapılan işlem idempotent olmalıdır: aynı istek birden fazla kez çalışsa bile tek kez çalışmışla aynı iş sonucunu üretmelidir. Örneğin aynı ödeme anahtarıyla ikinci kayıt oluşturmamak buna örnektir.

Kontrol listesi

  • Paylaşılan mutable veriyi belirleyin.
  • Birden fazla adımın atomik olması gerekip gerekmediğini sorun.
  • Lock sırasını standartlaştırın.
  • Concurrency testlerinde tek bir zamanlamaya güvenmeyin.
  • Dağıtık ortamda process içi lock’un sınırını unutmayın.