Java'da thread'ler işletim sisteminin gerçek paralel çalışan iş parçacıklarıyken, Python'da GIL adı verilen kilit yüzünden thread'ler CPU-bound işlerde gerçek paralellik sağlamaz; bu yüzden Python'da ağır hesaplama gerektiren görevler için genellikle multiprocessing tercih edilir. Kısacası mantık farkı şudur: Java'nın thread modeli işletim sistemiyle bire bir konuşurken, Python'daki threading modülü bir dizi işi sırayla, hızlıca geçiş yaparak yürütür; gerçek paralel hesaplama isteniyorsa Python'da ayrı process'lere ihtiyaç duyulur. Bu yazıda eşzamanlılık ile paralellik arasındaki temel ayrımdan başlayarak, GIL'in neden var olduğunu ve I/O-bound ile CPU-bound işlerde hangi yaklaşımın mantıklı olduğunu somut kod örnekleriyle inceleyeceğiz.
Eşzamanlılık ile Paralellik Arasındaki Fark: Gerçek Hayattan Bir Benzetme
Eşzamanlılık (concurrency) ve paralellik (parallelism) kavramları sıkça birbirine karıştırılır, oysa aralarındaki fark eşzamanlılık programlamasını anlamanın temelini oluşturur. Eşzamanlılık, birden fazla işin aynı anda "yönetiliyor" gibi görünmesi ama gerçekte belirli bir anda yalnızca birinin işlenmesidir; işler arasında hızlı geçişler yapılır ve kullanıcıya sanki hepsi birlikte ilerliyormuş hissi verilir. Paralellik ise gerçekten aynı anda, farklı işlemci çekirdeklerinde birden fazla işin fiziksel olarak yürütülmesidir.
Bunu somutlaştırmak için basit bir gişe benzetmesi kullanılabilir: Tek bir kasiyerin sırayla birden fazla müşteriyle ilgilenmesi, her müşteriye kısa kısa zaman ayırıp diğerine geçmesi eşzamanlılığa karşılık gelir; kasiyer aynı anda tek bir müşteriyle gerçekten ilgilenir ama hızlı geçişler sayesinde herkes hizmet almış gibi görünür. Buna karşılık, gişede aynı anda çalışan üç ayrı kasiyer varsa ve her biri kendi müşterisiyle gerçekten aynı zaman diliminde ilgileniyorsa bu paralelliktir. İşlerin gerçekten aynı anda mı yoksa çok hızlı sırayla mı yürütüldüğü, arka planda hangi mekanizmanın çalıştığını doğrudan belirler.
Bu ayrım, bir programcının thread mi yoksa process mi kullanacağına karar verirken kritik önem taşır. Bazı işler (örneğin ağdan veri beklemek) doğası gereği bekleme süresi içerir ve bu bekleme sırasında başka işlere geçmek yeterlidir; bu durumda eşzamanlılık mantığı işe yarar. Ama yoğun hesaplama gerektiren işlerde gerçek paralellik olmadan performans kazancı sağlamak mümkün değildir. Bu noktada hangi dilin hangi mekanizmayı nasıl sunduğunu bilmek, doğru mimari kararı vermenin ön koşuludur. Kendi seviyeni ölçmek ve bu tür kavramsal ayrımlara ne kadar hakim olduğunu görmek istiyorsan ücretsiz kodlama bilgisi testi ile hızlı bir öz değerlendirme yapabilirsin.
Python'da GIL (Global Interpreter Lock) Nedir, Neden Var?

GIL, yani Global Interpreter Lock, Python'un standart yorumlayıcısında aynı anda yalnızca bir thread'in Python bytecode'unu çalıştırmasına izin veren bir kilit mekanizmasıdır. Bir Python programında onlarca thread oluşturulsa bile, herhangi bir anda gerçekten çalışan yalnızca bir tanesidir; diğerleri sırasını beklemektedir. Bu, ilk bakışta bir kısıtlama gibi görünse de rastgele konulmuş bir kural değildir; belirli bir mühendislik problemine çözüm olarak tasarlanmıştır.
GIL'in var olmasının temel nedeni, Python'un bellek yönetiminde kullandığı referans sayımı (reference counting) mekanizmasının güvenliğini korumaktır. Python'daki her nesnenin kaç yerden referans aldığını tutan bir sayaç vardır ve bu sayaç sıfıra düştüğünde nesne bellekten temizlenir. Eğer birden fazla thread bu sayaçları aynı anda, kilitlenmeden değiştirebilseydi, yarış durumları (race condition) oluşur ve bellek bozulmaları, çökmeler veya bellek sızıntıları ortaya çıkabilirdi. GIL, bu sayaçların her zaman tek bir thread tarafından güvenle güncellenmesini garanti ederek yorumlayıcının iç tutarlılığını korur.
Pratikte bunun anlamı şudur: CPU-bound, yani yoğun hesaplama gerektiren bir işte birden fazla thread oluştursanız bile, bu thread'ler GIL'i sırayla alıp bırakarak çalışır; herhangi bir anda yalnızca biri gerçekten işlem yapar, diğerleri bekler. Dört çekirdekli bir işlemcide dört thread açsanız da, CPU-bound bir hesaplama söz konusuysa toplam işlem süresi tek thread'le neredeyse aynı kalır, çünkü gerçek paralel hesaplama gerçekleşmez. Bu mekanizmanın işleyişini ve etkilerini uygulamalı örneklerle görmek isteyenler için 1-1 özel Python dersleri içinde bu konular somut senaryolarla ele alınır. Önemli olan nokta, GIL'in Python'un thread davranışını nasıl şekillendirdiğini kavramsal olarak anlamaktır; bu anlayış, hangi işte hangi eşzamanlılık aracının seçileceğine dair doğru kararın temelini oluşturur.
I/O-bound ve CPU-bound İşler: Python'da Hangi Durumda Threading, Hangi Durumda Multiprocessing?
I/O-bound bir iş, işlemcinin çoğu zamanını hesaplama yaparak değil, dış bir kaynağı bekleyerek geçirdiği iştir; dosya okuma-yazma, ağ üzerinden veri isteği gönderme veya bir veritabanından yanıt bekleme bu kategoriye girer. Bu tür işlerde thread bekleme durumuna geçtiği anda GIL'i serbest bırakır, böylece başka bir thread çalışmaya başlayabilir. Bu yüzden I/O-bound senaryolarda threading kullanmak hâlâ mantıklıdır: thread'ler gerçek paralel çalışmasa da, bekleme süreleri örtüştüğü için toplam geçen süre önemli ölçüde kısalır.
import threading
import time
def veri_indir(kaynak_no):
print(f"Kaynak {kaynak_no}: indirme başladı")
time.sleep(2) # ağ beklemesini simüle eder
print(f"Kaynak {kaynak_no}: indirme tamamlandı")
thread_listesi = []
for i in range(3):
t = threading.Thread(target=veri_indir, args=(i,))
thread_listesi.append(t)
t.start()
for t in thread_listesi:
t.join()
print("Tüm indirmeler bitti")
CPU-bound bir iş ise işlemcinin sürekli hesaplama yaptığı, bekleme içermeyen iştir; büyük sayılarla matematiksel işlem yapmak veya uzun bir listeyi işlemek buna örnektir. Bu tür işlerde GIL serbest bırakılmadığı için threading gerçek bir hız kazancı sağlamaz. Bunun yerine multiprocessing modülü tercih edilir, çünkü her process kendi Python yorumlayıcısına ve kendi GIL'ine sahiptir; işletim sistemi bu process'leri farklı çekirdeklerde gerçekten paralel çalıştırabilir.
from multiprocessing import Process
import math
def yogun_hesaplama(sayi):
sonuc = sum(math.sqrt(i) for i in range(sayi))
print(f"{sayi} için hesaplama tamamlandı: {sonuc:.2f}")
if __name__ == "__main__":
islemler = []
for n in [5_000_000, 5_000_000, 5_000_000]:
p = Process(target=yogun_hesaplama, args=(n,))
islemler.append(p)
p.start()
for p in islemler:
p.join()
print("Tüm hesaplamalar bitti")
Bu iki örnek arasındaki fark yalnızca sözdizimi değil, altta yatan çalışma mantığıdır: threading örneğinde bekleme süreleri örtüşürken CPU gerçekte boşta kalır ve bu boşluk başka thread'ler tarafından değerlendirilir; multiprocessing örneğinde ise her process kendi belleğinde, kendi yorumlayıcısıyla bağımsız çalışır ve işletim sistemi bu process'leri farklı çekirdeklere dağıtabilir.
Java'nın Thread Modeli: JVM Native Thread Yönetimi
Java'da bir Thread oluşturduğunda, arka planda JVM bu iş parçacığını doğrudan işletim sisteminin kendi native thread yapısına eşler. Yani Java thread'leri, Python'daki gibi tek bir yorumlayıcı döngüsünün içinde sırayla çalıştırılan yapılar değil, işletim sisteminin zamanlayıcısı (scheduler) tarafından yönetilen gerçek, bağımsız çalışma birimleridir. Bu yaklaşım kavramsal olarak basittir: JVM, thread yönetimini kendi başına icat etmez, altında çalışan işletim sisteminin zaten var olan mekanizmasını kullanır.
Bunun pratikteki en önemli sonucu, Python'u öğrenen birinin ilk duyduğunda şaşırdığı bir farktır: Java'da GIL benzeri bir global kilit yoktur. Bu, birden fazla thread'in gerçekten aynı anda, farklı CPU çekirdeklerinde paralel çalışabileceği anlamına gelir. CPU'ya yoğun bir hesaplama işini dört thread'e bölersen ve makinende dört çekirdek varsa, bu iş teorik olarak dört kat hızlanabilir — Python threading'de CPU-bound işlerde yaşanan "hiç hızlanmama" sorunu Java'da bu şekliyle karşına çıkmaz.
Ancak bu özgürlüğün bir bedeli vardır. Thread'ler aynı bellek alanını paylaştığı için, birden fazla thread aynı veriye aynı anda erişip değiştirmeye çalıştığında veri tutarsızlığı riski doğar. Python'da GIL bu tür çakışmaları büyük ölçüde kendiliğinden engellerken, Java'da bu sorumluluk geliştiriciye aittir. Bu yüzden Java'da synchronized anahtar kelimesi ve Lock arayüzü gibi senkronizasyon araçları merkezi bir öneme sahiptir; paylaşımlı bir değişkene erişimi kilitlemeden bırakmak, üretim ortamında bulunması zor hatalara yol açabilir.
Basit bir örnekle bu mantığı somutlaştıralım:
public class ThreadOrnek {
private static int sayac = 0;
public static synchronized void arttir() {
sayac++;
}
public static void main(String[] args) throws InterruptedException {
Runnable gorev = () -> {
for (int i = 0; i < 1000; i++) {
arttir();
}
};
Thread t1 = new Thread(gorev);
Thread t2 = new Thread(gorev);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Sonuc: " + sayac);
}
}
Buradaki synchronized ifadesi olmasaydı, iki thread aynı anda sayac değişkenine erişip birbirinin işlemini ezebilir ve sonuç beklenenden düşük çıkabilirdi. Bu tür paylaşımlı bellek senaryolarını mantığıyla kavramak, Java'nın nesne yönelimli yapısını ve eşzamanlılık modelini bir bütün olarak öğrenmenin parçasıdır; konuyu sıfırdan, uygulamalı şekilde ilerletmek isteyenler birebir Java özel dersleri kapsamında bu tür senkronizasyon senaryolarını canlı örneklerle çalışabilir. Önemli olan, hangi JVM sürümünün hangi ek API'yi getirdiğini ezberlemek değil, paylaşımlı bellek modelinin neden senkronizasyon gerektirdiğini kavramaktır — bu mantık, üzerine yeni araçlar eklense de değişmez.
Python Threading, Python Multiprocessing ve Java Thread Karşılaştırması

Şimdiye kadar anlatılanları yan yana koyduğumuzda, üç yaklaşımın birbirinden nerede ayrıldığı çok daha net görünür. Aşağıdaki tablo, bellek modeli, paralellik desteği, tipik kullanım senaryosu ve kaynak tüketimi açısından bir özet sunuyor:
| Özellik | Python Threading | Python Multiprocessing | Java Thread |
|---|---|---|---|
| Bellek modeli | Paylaşımlı (aynı process içinde) | Ayrı (her process kendi belleğinde) | Paylaşımlı (aynı process içinde) |
| Paralellik desteği | GIL nedeniyle CPU-bound işte sınırlı | GIL sınırlaması yok, gerçek paralellik | Global kilit yok, gerçek paralellik |
| Tipik kullanım senaryosu | I/O-bound işler (ağ, dosya, bekleme) | CPU-bound işler (yoğun hesaplama) | Hem I/O-bound hem CPU-bound işler |
| Oluşturma maliyeti | Düşük, hafif | Yüksek, her process ayrı bellek alır | Orta, native thread maliyeti taşır |
Tablodan çıkan sonuç şudur: Python'da thread'ler hafif ama CPU-bound işte sınırlıdır; process'ler bu sınırı aşar ama bellek ve başlatma maliyeti getirir. Java'da ise thread zaten paylaşımlı bellekle ve gerçek paralellikle çalıştığı için, çoğu senaryoda ek bir "process" katmanına ihtiyaç duyulmadan hem I/O-bound hem CPU-bound işler aynı thread modeliyle ele alınabilir. Bu fark, iki dilin eşzamanlılığa neden farklı felsefelerle yaklaştığını gösteren en somut noktadır: Python'da doğru aracı seçmek görevin niteliğine bağlıyken, Java'da thread çoğu zaman tek başına yeterli bir araçtır.
Hangi Durumda Hangi Yaklaşımı Seçmelisin? Pratik Karar Kriterleri
Teoriyi bir kenara bırakıp gerçek bir proje önünde durduğunda kendine sorman gereken birkaç somut soru vardır. Aşağıdaki sıralı liste, bu kararı adım adım netleştirmek için kullanılabilir:
- İşlerin veri paylaşmasına ihtiyacı var mı? Thread'ler aynı belleği paylaştığı için veri alışverişi kolaydır; process'ler arasında veri taşımak için ise
Queue,Pipegibi süreçler arası iletişim (IPC) mekanizmaları gerekir ve bu ek karmaşıklık getirir. - Yapılan iş CPU-bound mu, I/O-bound mu? Yoğun hesaplama (matematiksel işlem, sıkıştırma, görüntü işleme) CPU-bound'dur ve Python'da multiprocessing gerektirir; bekleme ağırlıklı iş (ağ isteği, dosya okuma) I/O-bound'dur ve threading yeterlidir.
- Bellek kullanımı ne kadar kritik? Her process kendi belleğini kopyaladığı için process'ler thread'lere göre çok daha fazla bellek tüketir; sınırlı kaynaklı bir ortamda çok sayıda process açmak sistemi zorlayabilir.
- Kaç çekirdek üzerinde çalışıyorsun ve ölçeklenebilirlik ne kadar önemli? Çok çekirdekli bir makinede CPU-bound işi gerçekten hızlandırmak istiyorsan, Python'da threading bu amacı karşılamaz; iş yükünü çekirdek sayısı kadar process'e bölmek gerekir.
- Hangi dille çalışıyorsun? Java'da thread çoğu zaman tek başına yeterlidir çünkü paralellik zaten native olarak desteklenir; Python'da ise CPU-bound bir iş söz konusu olduğunda threading yerine doğrudan multiprocessing'e yönelmek gerekir.
- Kod karmaşıklığını ne kadar üstlenmeye hazırsın? Senkronizasyon hataları ve yarış koşulları (race condition) hem thread hem process ortamında ortaya çıkabilir; bu riski en aza indirmek için basit, izole edilebilir görevlerle başlamak mantıklıdır.
Bu kriterler kâğıt üzerinde net görünse de, gerçek bir projede hangi yaklaşımın doğru olduğuna karar vermek genellikle deneyimle netleşir. Kod yazarken karşılaşılan bir race condition'ı veya beklenmedik bir performans sorununu birebir bir eğitmenle tartışmak, bu kararı çok daha hızlı içselleştirmeni sağlar; tereddüt yaşadığın noktalarda 1-1 özel ders desteğiyle kendi kod örneklerin üzerinden ilerlemek, soyut kuralları somut alışkanlığa dönüştürmenin en pratik yollarından biridir.
Eşzamanlılık Kavramlarını Pratikle Pekiştirme
Buraya kadar anlatılanların hepsi kâğıt üzerinde mantıklı görünür ama eşzamanlılık, sadece okuyarak kavranacak bir konu değildir. GIL'in bir thread'i nasıl bekletti��ini, iki process'in belleği neden paylaşamadığını ya da Java'da bir thread'in gerçekten başka bir çekirdekte koştuğunu görmek istiyorsan, küçük ve kontrollü deneyler yapman gerekir. Örneğin aynı CPU-bound fonksiyonu önce tek thread'le, sonra threading ile, sonra multiprocessing ile çalıştırıp süreleri karşılaştırmak, teorik bilgiyi somut bir gözleme dönüştürür. Bu tür küçük deneyler, "GIL var" bilgisini "GIL şu durumda süreyi şu kadar etkiliyor" seviyesine taşır.
Bu tür pratikler tek seferlik değil, tekrar edilen bir alışkanlık olmalı. Bir konuyu ilk seferde tam oturtamamak normaldir; bu yüzden ders kayıtlarına dönüp aynı örneği ikinci kez izlemek, ardından benzer bir problemi kendi başına kod yazarak tekrar kurmak kalıcı öğrenmeyi güçlendirir. Günlük kısa alıştırma sorularıyla thread ve process kavramlarını farklı senaryolarda tekrar tekrar görmek, ezberden çok "bu problemde hangi araç mantıklı" sorusuna hızlı cevap verme becerisi kazandırır. Küçük deneme-yanılma döngüsü, eşzamanlılık gibi soyut konularda en etkili öğrenme yoludur.
Kariyer hedefin hangi dile yakınsa pratik akışını da ona göre kurmalısın. Java tarafında ilerlemek istiyorsan basit bir üretici-tüketici senaryosunu Thread sınıfıyla kurup senkronizasyon sorunlarını (race condition, deadlock) elle deneyimlemek çok öğreticidir. Python tarafında ise aynı problemi hem threading hem multiprocessing ile çözüp GIL'in etkisini kendi gözlerinle ölçmek gerekir. Bu tür uygulamalı alıştırmaları birebir Python dersleri kapsamındaki video içeriklerle birleştirip kendi hızında tekrar tekrar izlemek, konuyu tek bir dersle geçiştirmek yerine sindirerek öğrenmeyi mümkün kılar. Sonuç olarak eşzamanlılık, teoriyi bir kez anlayıp bırakılacak bir konu değil; küçük kod parçalarıyla sürekli test edilerek pekiştirilmesi gereken bir refleks geliştirme sürecidir.
Sık Sorulan Sorular
Python'da GIL tüm thread'leri tamamen engelliyor mu, hiç paralel çalışma olmaz mı?
Hayır, tamamen engellemez. GIL yalnızca aynı anda bir thread'in Python bayt kodu çalıştırmasına izin verir; ancak dosya okuma, ağ isteği gibi I/O bekleme süreleri sırasında GIL serbest bırakılır ve diğer thread'ler çalışabilir. Bu yüzden I/O-bound işlerde threading hâlâ gerçek bir hız kazancı sağlar, kısıtlama esasen CPU'ya bağlı hesaplamalarda hissedilir.
Java'da thread kullanırken Python'daki GIL gibi bir kısıtlama var mı?
Java'nın thread modelinde GIL benzeri merkezi bir kilit yoktur; JVM thread'leri işletim sisteminin native thread'leriyle eşleştirir ve bunlar gerçekten farklı çekirdeklerde eşzamanlı çalışabilir. Bu, Java'da CPU-bound bir işi thread'lere bölmenin Python'daki threading'e göre daha doğrudan bir performans artışı sağlamasının temel nedenidir.
I/O-bound bir işte multiprocessing kullanmak neden gereksiz olabilir?
Multiprocessing her process için ayrı bellek alanı ve ayrı bir Python yorumlayıcısı başlatır; bu da thread açmaya göre çok daha fazla kaynak ve başlangıç maliyeti demektir. I/O-bound bir işte zaten GIL'in etkisi minimum olduğundan, bu ek maliyeti üstlenmek yerine daha hafif olan threading veya asenkron yaklaşımlar genellikle yeterli olur.
Python'da threading ile multiprocessing arasında bellek kullanımı açısından temel fark nedir?
Threading'de aynı process içindeki tüm thread'ler aynı bellek alanını ve aynı değişkenleri paylaşır, bu da veri paylaşımını kolaylaştırır ama senkronizasyon riskini artırır. Multiprocessing'de ise her process kendi izole belleğine sahiptir; veri paylaşımı için özel iletişim mekanizmaları gerekir ama bu izolasyon aynı zamanda güvenliği artırır.
Eşzamanlılığı öğrenmeye Java ile mi Python ile mi başlamalıyım?
Bu, dilin kendisinden çok hangi mantığı önce oturtmak istediğinle ilgilidir. Java'nın native thread modeli, thread yaşam döngüsünü ve senkronizasyonu daha "ham" haliyle görmeni sağlar; Python ise GIL sayesinde önce daha az karmaşık senaryolarla başlayıp kavramları basitten karmaşığa doğru öğrenmene imkân tanır. Her iki yol da doğrudur, önemli olan seçtiğin dilde küçük örnekler üzerinden düzenli pratik yapmaktır.
Thread ve process arasındaki temel fark nedir?
Bir process, kendi bağımsız bellek alanına ve kaynaklarına sahip çalışan bir programdır; thread ise bir process içinde çalışan ve o process'in belleğini diğer thread'lerle paylaşan daha küçük bir yürütme birimidir. Bu yüzden thread'ler arası iletişim genellikle daha hızlı ama daha risklidir, process'ler arası iletişim ise daha güvenli ama daha maliyetlidir.
Bir işin CPU-bound mu yoksa I/O-bound mu olduğunu nasıl anlarım?
İşin çoğu zamanını işlemcide hesaplama yaparak mı (matematiksel işlemler, döngüler, veri işleme) yoksa dış bir kaynağı bekleyerek mi (disk okuma, ağ isteği, veritabanı sorgusu) geçirdiğine bakman yeterlidir. Hesaplama ağırlıklı işler CPU-bound, bekleme ağırlıklı işler I/O-bound olarak sınıflandırılır ve bu ayrım hangi eşzamanlılık aracını seçeceğini doğrudan belirler.
Thread, process, GIL gibi kavramlar ilk bakışta soyut görünse de mantığı bir kez oturduğunda hem Java hem Python'da doğru aracı seçmek çok daha kolaylaşır. Bu kavramları kendi kod denemelerinle test etmek istersen ücretsiz kodlama bilgisi testi ile mevcut seviyeni ölçerek nereden devam edeceğine karar verebilirsin.