Concurrency (Eşzamanlılık): Lock, Race Condition ve Deadlock
Modern yazılım sistemleri, aynı anda birden fazla işlemi gerçekleştirmek zorundadır. Kullanıcılar aynı anda sipariş verir, aynı bilet için rezervasyon yapar veya aynı dokümanı düzenler. İşte tam bu noktada Concurrency (Eşzamanlılık) devreye girer.
Bu yazıda, eşzamanlı programlamanın temel kavramlarını, karşılaşılan sorunları ve çözüm tekniklerini detaylıca inceleyeceğiz.
1. Concurrency Nedir?
Concurrency, birden fazla işlemin aynı zaman dilimi içinde yürütülmesidir. Bu işlemler:
- Paralel çalışabilir (farklı CPU çekirdeklerinde aynı anda)
- Paralel olmayan şekilde çalışabilir (tek CPU'da zaman dilimleriyle)
Gerçek Hayat Örneği: Bilet Satış Sistemi
Bir konser için 100 bilet satılıyor. 200 kullanıcı aynı anda satın almaya çalışıyor. Eğer concurrency yönetimi yoksa, aynı bileti iki kişi satın alabilir!
2. Race Condition (Yarış Koşulu)
Mantık
Race condition, birden fazla işlemin paylaşılan bir kaynağa aynı anda erişmeye çalışması ve işlemlerin zamanlamasına bağlı olarak sonucun değişmesi durumudur.
Klasik Örnek: Bakiye Güncelleme
# İki thread'in aynı anda çalıştığını düşünelim
bakiye = 100
# Thread 1
def para_cek_100():
if bakiye >= 100: # 1. okuma
bakiye = bakiye - 100 # 2. yazma
# Thread 2
def para_cek_50():
if bakiye >= 50: # 1. okuma
bakiye = bakiye - 50 # 2. yazma
Race Condition Senaryosu
Başlangıç: bakiye = 100
Zaman:
T1: Thread 1 okur -> bakiye = 100
T2: Thread 2 okur -> bakiye = 100
T3: Thread 1 yazar -> bakiye = 0 (100 - 100)
T4: Thread 2 yazar -> bakiye = -50 (100 - 50 ???)
Sonuç: bakiye = -50 (Bu imkansız!)
Çözüm: Atomic Operasyonlar
import threading
lock = threading.Lock()
bakiye = 100
def guvenli_para_cek_100():
global bakiye
with lock: # Thread-safe blok
if bakiye >= 100:
bakiye -= 100
def guvenli_para_cek_50():
global bakiye
with lock:
if bakiye >= 50:
bakiye -= 50
3. Lock (Kilit) Mekanizmaları
Lock'lar, paylaşılan kaynaklara aynı anda sadece bir thread'in erişmesini sağlayan mekanizmalardır.
3.1 Mutex (Mutual Exclusion - Karşılıklı Dışlama)
En temel lock türüdür. Bir thread lock'u alır, işini bitirir, lock'u bırakır.
import threading
mutex = threading.Lock()
shared_counter = 0
def increment_counter():
global shared_counter
for _ in range(1000000):
with mutex: # Lock al
shared_counter += 1 # Kritik bölge
# Lock otomatik bırakılır
# 10 thread oluştur
threads = [threading.Thread(target=increment_counter) for _ in range(10)]
for t in threads: t.start()
for t in threads: t.join()
print(shared_counter) # 10,000,000 (her zaman doğru!)
3.2 Semaphore (Semafor)
Belirli sayıda thread'in aynı anda kaynağa erişmesine izin verir.
import threading
import time
# Aynı anda sadece 3 thread veritabanı bağlantısı kullanabilsin
connection_pool = threading.Semaphore(3)
def database_query(query):
with connection_pool:
print(f"{threading.current_thread().name} bağlantı aldı")
time.sleep(2) # SQL sorgusu çalışıyor
print(f"{threading.current_thread().name} bağlantı bıraktı")
# 10 thread çalıştır
for i in range(10):
t = threading.Thread(target=database_query, args=(f"SELECT {i}",))
t.start()
3.3 Read-Write Lock (Okuyucu-Yazıcı Kilidi)
Okuma işlemleri eşzamanlı yapılabilirken, yazma işlemleri özel erişim gerektirir.
import threading
class ReadWriteLock:
def __init__(self):
self.readers = 0
self.read_lock = threading.Lock()
self.write_lock = threading.Lock()
def acquire_read(self):
with self.read_lock:
self.readers += 1
if self.readers == 1:
self.write_lock.acquire() # İlk okuyucu yazıcıyı engeller
def release_read(self):
with self.read_lock:
self.readers -= 1
if self.readers == 0:
self.write_lock.release() # Son okuyucu yazıcıya izin verir
def acquire_write(self):
self.write_lock.acquire() # Yazmak için bekle
def release_write(self):
self.write_lock.release()
# Kullanım
rw_lock = ReadWriteLock()
cache = {}
def read_data(key):
rw_lock.acquire_read()
try:
return cache.get(key)
finally:
rw_lock.release_read()
def write_data(key, value):
rw_lock.acquire_write()
try:
cache[key] = value
finally:
rw_lock.release_write()
3.4 Spinlock (Dönel Kilit)
Thread, lock serbest kalana kadar boşta beklemez, sürekli kontrol eder (döner).
import threading
import time
class Spinlock:
def __init__(self):
self.locked = False
self.lock = threading.Lock()
def acquire(self):
while True:
with self.lock:
if not self.locked:
self.locked = True
return
# Kısa bir bekleme (CPU yakmamak için)
time.sleep(0.000001)
def release(self):
with self.lock:
self.locked = False
# Spinlock, çok kısa süreli kritik bölgeler için idealdir
4. Deadlock (Kilitlenme)
Mantık
Deadlock, iki veya daha fazla işlemin birbirlerinin tuttuğu kaynakları beklerken sonsuza kadar bloke olması durumudur.
Deadlock için 4 Koşul (Coffman Şartları)
- Mutual Exclusion (Karşılıklı Dışlama): Kaynaklar paylaşılamaz
- Hold and Wait (Tut ve Bekle): Bir kaynağı tutarken başka kaynak bekleme
- No Preemption (Zorla Alma Yok): Kaynaklar zorla alınamaz
- Circular Wait (Dairesel Bekleme): Her thread bir sonrakini bekler
Klasik Deadlock Örneği
import threading
import time
lock1 = threading.Lock()
lock2 = threading.Lock()
def thread1():
with lock1:
print("Thread 1: lock1 aldı")
time.sleep(0.1) # Thread2'nin lock2'yi almasına izin ver
print("Thread 1: lock2 bekliyor...")
with lock2: # Deadlock burada!
print("Thread 1: lock2 aldı")
def thread2():
with lock2:
print("Thread 2: lock2 aldı")
time.sleep(0.1)
print("Thread 2: lock1 bekliyor...")
with lock1: # Deadlock burada!
print("Thread 2: lock1 aldı")
# Thread'leri başlat
t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)
t1.start()
t2.start()
t1.join()
t2.join()
Deadlock Çözümleri
Çözüm 1: Lock Sıralaması (Lock Ordering)
# Her zaman lock'ları aynı sırada al!
def thread1_safe():
with lock1:
with lock2:
print("Thread 1: her iki lock da alındı")
def thread2_safe():
with lock1: # Aynı sıra!
with lock2:
print("Thread 2: her iki lock da alındı")
Çözüm 2: Zaman Aşımı (Timeout)
def thread_with_timeout():
acquired_lock1 = lock1.acquire(timeout=1)
if not acquired_lock1:
print("lock1 alınamadı, iptal")
return
try:
acquired_lock2 = lock2.acquire(timeout=1)
if not acquired_lock2:
print("lock2 alınamadı, lock1 bırakılıyor")
return
try:
print("Kritik işlem yapılıyor...")
finally:
lock2.release()
finally:
lock1.release()
Çözüm 3: Banker's Algorithm (Teorik)
# Kaynak tahsisini merkezi olarak yönet
class ResourceManager:
def __init__(self, total_resources):
self.available = total_resources.copy()
self.allocation = {}
self.lock = threading.Lock()
def request_resources(self, process_id, request):
with self.lock:
# Deadlock oluşmayacağını kontrol et
if all(request[r] <= self.available[r] for r in request):
# Geçici olarak tahsis et
for r in request:
self.available[r] -= request[r]
self.allocation[process_id] = request
return True
return False
5. Veritabanı Seviyesinde Concurrency
5.1 Optimistic Locking (İyimser Kilit)
-- Tabloya version sütunu ekle
ALTER TABLE products ADD COLUMN version INTEGER DEFAULT 0;
-- Güncelleme yaparken version kontrolü
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 123 AND version = 5;
-- Etkilenen satır sayısı 0 ise, başkası güncellemiş demektir
5.2 Pessimistic Locking (Kötümser Kilit)
-- Satırı kilitle (başkası okuyamaz/yazamaz)
BEGIN;
SELECT * FROM products WHERE id = 123 FOR UPDATE;
-- İşlemler...
UPDATE products SET stock = stock - 1 WHERE id = 123;
COMMIT;
6. Modern Concurrency Araçları
6.1 Python Asyncio (Tek Thread'li Concurrency)
import asyncio
async def fetch_data(url):
print(f"İndiriliyor: {url}")
await asyncio.sleep(1) # I/O işlemi simülasyonu
return f"Veri: {url}"
async def main():
urls = ['url1', 'url2', 'url3']
tasks = [fetch_data(url) for url in urls]
results = await asyncio.gather(*tasks)
print(results)
# Çalıştır
asyncio.run(main())
6.2 Go Routines (Goroutines)
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var counter int
var mutex sync.Mutex
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mutex.Lock()
counter++
mutex.Unlock()
}()
}
wg.Wait()
fmt.Println("Counter:", counter)
}
6.3 Actor Model (Akka, Erlang)
// Actor modelinde her aktör kendi durumuna sahiptir ve mesajlarla iletişir
public class BankAccount extends AbstractActor {
private int balance = 0;
@Override
public Receive createReceive() {
return receiveBuilder()
.match(Deposit.class, deposit -> {
balance += deposit.amount;
sender().tell(new DepositResult(true), self());
})
.match(Withdraw.class, withdraw -> {
if (balance >= withdraw.amount) {
balance -= withdraw.amount;
sender().tell(new WithdrawResult(true), self());
} else {
sender().tell(new WithdrawResult(false), self());
}
})
.build();
}
}
7. Pratik İpuçları ve En İyi Pratikler
Lock Kullanırken Dikkat Edilecekler
-
Lock'ları mümkün olduğunca kısa tutun
# KÖTÜ with lock: veri_oku() hesaplama_yap() dosya_yaz() api_cagir() # 2 saniye bekler! # İYİ with lock: veri_oku() temp = hesaplama_yap() dosya_yaz(temp) # lock dışında api_cagir(temp) # lock dışında -
İç içe lock'lardan kaçının
# KÖTÜ - deadlock riski with lock_a: with lock_b: islem_yap() # İYİ - lock'ları aynı anda al with lock_a, lock_b: islem_yap() -
Thread-safe veri yapıları kullanın
from queue import Queue from collections import deque # Thread-safe kuyruk safe_queue = Queue() # Thread-safe değil! unsafe_list = []
Race Condition Tespiti için Araçlar
# Python'da race condition tespiti
pip install pytest-xdist
pytest -n 10 test_file.py
# Thread sanitizer (C/C++)
gcc -fsanitize=thread -g program.c -o program
./program
8. Gerçek Hayat Senaryosu: Stok Yönetimi
Tam bir e-ticaret stok yönetimi örneği:
import threading
import time
import random
from dataclasses import dataclass
from typing import Dict
@dataclass
class Product:
id: int
name: str
stock: int
version: int = 0
class InventoryService:
def __init__(self):
self.products: Dict[int, Product] = {}
self.locks: Dict[int, threading.Lock] = {}
self.global_lock = threading.Lock()
def _get_lock(self, product_id: int) -> threading.Lock:
# Her ürün için ayrı lock (fine-grained locking)
with self.global_lock:
if product_id not in self.locks:
self.locks[product_id] = threading.Lock()
return self.locks[product_id]
def add_product(self, product: Product):
self.products[product.id] = product
def purchase_product(self, product_id: int, quantity: int) -> bool:
lock = self._get_lock(product_id)
with lock:
product = self.products.get(product_id)
if not product:
return False
# Stok kontrolü
if product.stock < quantity:
return False
# Stok güncelleme (atomic)
product.stock -= quantity
product.version += 1
# Satın alma log'u
print(f"[{threading.current_thread().name}] "
f"{product.name} - {quantity} adet satıldı. "
f"Kalan stok: {product.stock}")
return True
def get_stock(self, product_id: int) -> int:
lock = self._get_lock(product_id)
with lock:
product = self.products.get(product_id)
return product.stock if product else -1
# Simülasyon
def simulate_purchases(inventory: InventoryService, product_id: int):
for _ in range(100):
quantity = random.randint(1, 3)
success = inventory.purchase_product(product_id, quantity)
if not success:
# Stok bitmiş
break
time.sleep(random.uniform(0.01, 0.05))
# Test
inventory = InventoryService()
inventory.add_product(Product(id=1, name="Akıllı Telefon", stock=50))
threads = []
for i in range(10): # 10 kullanıcı aynı anda satın almaya çalışıyor
t = threading.Thread(
target=simulate_purchases,
args=(inventory, 1),
name=f"Kullanıcı-{i}"
)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"\nSon stok: {inventory.get_stock(1)}")
print(f"Toplam satılan: {50 - inventory.get_stock(1)}")
Özet: Concurrency Kontrol Listesi
- [ ] Race Condition riski var mı? Paylaşılan kaynakları kontrol edin
- [ ] Lock sıralaması tutarlı mı? Deadlock'tan kaçınmak için aynı sırayı kullanın
- [ ] Kritik bölge ne kadar uzun? Mümkün olduğunca kısa tutun
- [ ] Hangi lock tipi uygun? Mutex, semaphore, read-write?
- [ ] Veritabanı işlemleri için hangi izolasyon seviyesi? Optimistic vs Pessimistic
- [ ] Timeout mekanizması var mı? Sonsuz bekleme olmasın
- [ ] Test edildi mi? Yüksek eşzamanlılıkta test yapın
- [ ] Monitoring var mı? Deadlock ve contention metriklerini izleyin
Unutmayın: Concurrency sorunlarını test etmek zordur çünkü rastgele ortaya çıkarlar. "Test geçti, production'da patladı" senaryosu en çok burada görülür! Bu yüzden tasarım aşamasında doğru stratejiyi belirlemek kritik önem taşır.