Kafayı yiyecektim arkadaş. Şu CPU-bound işleri hızlandırmak için kolları sıvadım, Python'da güzelce threading modülünü kullandım, 4 çekirdekli işlemcime "hadi aslanım" dedim. Sonuç? Neredeyse tek thread'le çalıştığımdan farksız! Meğerse o meşhur GIL (Global Interpreter Lock) denen canavar, tüm işleri tek seferde bir thread'e kilitleyip, diğerlerini bekletiyormuş. Şaka gibi ama gerçek.
Basitçe anlatayım, Python belleği yönetirken (garbage collection, nesne referans sayıları) her şeyin düzgün gitmesi için bir kilit mekanizması kullanıyor. Bu kilit, aynı anda sadece BİR thread'in Python bytecode'unu çalıştırmasına izin veriyor. Yani sen 10 tane thread açsan da, aslında hepsi sırayla koşuyor. İşin komiği, bu sadece saf Python kodunda böyle. Eğer thread'lerin C extension (NumPy gibi) kullanıyorsa veya I/O-bound (dosya okuma, network) işler yapıyorsa, GIL serbest bırakılabiliyor ve o zaman multi-threading mantıklı oluyor.
Python:
import threading
counter = 0
def artir():
global counter
for _ in range(1000000):
counter += 1 # GIL burada her seferinde bir thread'in girmesine izin verir.
threads = []
for i in range(4):
t = threading.Thread(target=artir)
threads.append(t)
t.start()
for t in threads:
t.join()
print(counter) # Beklenen 4.000.000 olmaz, çok daha az çıkar!
İşte burası can alıcı nokta. C++'da böyle bir GIL yok. Sen std::thread ile gerçek anlamda paralel iş yaptırabiliyorsun. İşlemci çekirdeklerin hepsi %100'e yakın kullanılabiliyor. Tabii bunun bedeli de var: Race condition, deadlock gibi sorunlarla sen uğraşıyorsun. Python'daki GIL, bu tür sorunları büyük ölçüde önlüyor aslında, ama bedeli performans.
Peki ne yapacağız? CPU-bound işler için Python'da genellikle iki yol önerilir:
1. multiprocessing modülü: Her process'in kendi Python interpreter'ı ve belleği olduğu için GIL'den etkilenmez. Ama processler arası veri paylaşımı maliyetli.
2. asyncio ile I/O-bound işler: Burada thread değil, task'lar var ve GIL sorunu yok. Ağ işleri için birebir.
Ama itiraf edeyim, bazen "keşke Python da gerçek thread'leri desteklese" diye düşünmeden edemiyorum. Özellikle machine learning veya scientific computing gibi alanlarda C++/Rust'a kaçışın bir sebebi de bu bence.
Siz bu GIL belasıyla nasıl baş ediyorsunuz? Hiç "yeter artık" deyip saf C++'a veya Rust'a geçmeyi düşündünüz mü? Yoksa multiprocessing ve asyncio ile idare mi ediyorsunuz? Fikirlerinizi bekliyorum!