Python’da “neden 4 çekirdekli işlemcim varken kodum tek çekirdekte sürünüyor?” diye sorduğun oldu mu? Suçlu, Python’ın ünlü Global Interpreter Lock (GIL) mekanizması. Bu yazıda, multiprocessing modülüyle GIL’i tamamen devre dışı bırakıp CPU’nun tüm çekirdeklerini aynı anda nasıl çalıştırabileceğini adım adım göreceksin.
Multiprocessing Ne İşe Yarar?
Python’da thread’ler (iş parçacıkları) kullanırken bile, GIL adı verilen kilit yüzünden aynı anda yalnızca bir thread Python bytecode’u çalıştırabilir. Bu, ağ isteği bekleme gibi G/Ç-bound (I/O-bound) işlerde sorun değil; ama faktöriyel hesaplama, görüntü işleme ya da veri dönüştürme gibi CPU-bound (işlemci-yoğun) işlerde thread’ler birbirinin ayağına basar ve gerçek bir hız kazancı elde edemezsin.
multiprocessing modülü bu sorunu kökten çözer: Her worker’ı ayrı bir thread olarak değil, işletim sisteminin kendi başına yönettiği bağımsız bir process (süreç) olarak başlatır. Her process kendi Python yorumlayıcısına, kendi belleğine ve kendi GIL’ine sahip olduğu için, dört çekirdekli bir makinede dört farklı hesaplama gerçekten aynı anda yürüyebilir.
Bu projede, dört farklı büyüklükte faktöriyel hesaplayan dört bağımsız süreç başlatıyoruz ve hepsinin bitmesini bekleyip toplam süreyi ölçüyoruz. Sonuç, tek tek sırayla çalıştırmaya kıyasla belirgin bir hızlanma.
Kodun Anatomisi
Ortam Bilgisi: Bu örnek Ahmet Aksoy tarafından 26.05.2026 tarihinde, Python 3.12 ve Ubuntu 24.04 ortamında yazılmıştır.
Kodun kalbinde üç aşama var: CPU-bound bir fonksiyon tanımlamak, bu fonksiyonu ayrı süreçlere dağıtmak ve son olarak ana programın tüm süreçlerin bitmesini beklemesini sağlamak.
Öncelikle, her sürecin çalıştıracağı ağır işi tanımlıyoruz. Bu fonksiyon basit bir kümülatif çarpım döngüsü yürütüyor — amaç gerçek bir faktöriyel algoritması değil, çekirdekleri gerçekten meşgul edecek kadar yoğun bir iş yükü yaratmak:
import multiprocessing
import time
def compute_heavy_factorial(name: str, number: int) -> int:
print(f"[Process {name}] Started for number: {number}...")
start_time = time.time()
result = 1
for i in range(1, number + 1):
result *= i
duration = time.time() - start_time
print(f"[Process {name}] Finished in {duration:.4f} seconds.")
return result
Ardından ana programda, her iş yükü için bir multiprocessing.Process nesnesi oluşturuyoruz. target parametresi hangi fonksiyonun çalışacağını, args ise ona hangi argümanların gönderileceğini belirtiyor. .start() çağrısı, işletim sistemine “yeni bir Python yorumlayıcısı başlat ve bu işi ona ver” komutunu veriyor:
if __name__ == "__main__":
workloads = [50000, 55000, 60000, 65000]
processes = []
for idx, size in enumerate(workloads, start=1):
p = multiprocessing.Process(
target=compute_heavy_factorial,
args=(f"Worker-Core-{idx}", size)
)
processes.append(p)
p.start()
for p in processes:
p.join()
Buradaki en kritik detay if __name__ == "__main__": koruması. multiprocessing, yeni bir süreç başlatırken ana dosyayı yeniden içe aktarır (import eder). Bu koruma olmasaydı, her yeni süreç dosyanın tamamını baştan çalıştırıp kendi alt süreçlerini oluşturmaya kalkışır — sonsuz, kontrolsüz bir süreç çoğalmasına yol açardı. .join() döngüsü ise ana programın, tüm işçiler işini bitirmeden ölçüm sonuçlarını yazdırıp kapanmasını engelliyor; adeta bir toplantıda herkesin konuşmasını bitirmesini beklemek gibi.
Nasıl Çalıştırılır?
Projeyi çalıştırmak için harici bir bağımlılığa ihtiyacın yok; multiprocessing standart kütüphanenin bir parçası. Tek gereken Python 3.x ve birden fazla çekirdeğe sahip bir makine (neredeyse her modern bilgisayar bu şartı sağlar).
python3 main.py
Terminalde dört “Worker-Core” satırının neredeyse aynı anda başladığını, ardından farklı sürelerde bitirdiklerini göreceksin:
--- Multiprocessing Computational Engine Initialized ---
[Process Worker-Core-1] Started for number: 50000...
[Process Worker-Core-2] Started for number: 55000...
[Process Worker-Core-3] Started for number: 60000...
[Process Worker-Core-4] Started for number: 65000...
--> Successfully launched 4 parallel worker processes. <--
[Process Worker-Core-1] Finished in 0.0412 seconds.
[Process Worker-Core-3] Finished in 0.0498 seconds.
[Process Worker-Core-2] Finished in 0.0503 seconds.
[Process Worker-Core-4] Finished in 0.0587 seconds.
--- Parallel Computational Lifecycle Concluded ---
Total processing block completion time: 0.0621 seconds.
Dikkat edersen, dört sürecin ayrı ayrı süreleri toplansa yaklaşık 0.2 saniye eder; ama hepsi paralel çalıştığı için toplam süre yalnızca en yavaş sürecinkine yakın çıkıyor. Süreçler ayrıca bitirme sırasına göre değil, işletim sisteminin zamanlamasına göre rastgele bir sırayla tamamlanabilir — bu da gerçek paralelliğin bir kanıtı.
Ne Öğrendik?
- Python’da GIL, thread’lerin CPU-bound işlerde gerçek paralellik sağlamasını engeller.
multiprocessing.Processher worker’ı kendi belleği ve kendi GIL’i olan ayrı bir işletim sistemi süreci olarak çalıştırarak bu sınırı aşar.if __name__ == "__main__":koruması, çoklu süreç kodunda sonsuz döngüye girmemek için zorunludur..start()bir süreci başlatır,.join()ise ana programın onu beklemesini sağlar — ikisi birlikte güvenli bir paralel iş akışı kurar.- Bu yaklaşım özellikle sayısal hesaplama, görüntü/video işleme ve büyük veri dönüştürme gibi işlemci-yoğun görevlerde anlamlı hız kazancı sağlar.
Kaynaklar ve Sonraki Adımlar
Multiprocessing’i thread’lerle karşılaştırıp hangisini ne zaman seçmen gerektiğini merak ediyorsan, Python’da Hız Yarışı: Multiprocessing mi, Threading mi? yazımıza göz atabilirsin. G/Ç-bound işler için ise modern eşzamanlılık yaklaşımı olan asyncio ve TaskGroup yazımız iyi bir sonraki durak olabilir. Sıradaki adım olarak multiprocessing.Pool veya süreçler arası veri paylaşımı için Queue ve Manager sınıflarını incelemeni öneririz.
Ahmet Aksoy
Not: Bu yazıda incelediğimiz kodu ve benzer projelerin kaynak kodlarını https://github.com/ahmetax/practical-python-examples adresinde bulabilirsiniz.



