12 - Eş zamanlılık, Paralellik ve Asenkron Programlama
2025
Bu hafta, programlarımızı daha verimli ve hızlı hale getirebilmek için kullanabileceğimiz üç önemli kavramı ele alacağız:
Eş zamanlılık (concurrency), paralellik (parallelism) ve asenkron programlama (asynchronous programming).
Bu kavramlar sayesinde, bir programın aynı anda birden fazla işi gerçekleştirmesi ya da beklemeden diğer işlemlere geçebilmesi mümkün hale gelir. Özellikle çok çekirdekli işlemcilere sahip olan bilgisayarlarda bu yetenekler büyük önem taşır.
threading
, multiprocessing
ve asyncio
kütüphanelerinin temel kullanımını öğrenmek,Bu konular, özellikle büyük ve kullanıcı etkileşimli yazılımlarda performans artırmak için gereklidir. Ders boyunca, hem teorik bilgileri hem de uygulamalı örnekleri birlikte göreceğiz.
threading
modülü kullanılır.multiprocessing
modülü kullanılır.asyncio
modülü kullanılır.Threading, aynı program içinde birden fazla iş parçacığının (thread) çalışmasını sağlayan bir tekniktir. Her thread, aynı anda farklı bir işlemi gerçekleştirebilir gibi çalışır. Bu, özellikle I/O-bound (yoğun) işlemlerde performans artırabilir.
import threading
def yaz():
for i in range(5):
print("Yazma işlemi")
def oku():
for i in range(5):
print("Okuma işlemi")
thread1 = threading.Thread(target=yaz)
thread2 = threading.Thread(target=oku)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
Açıklama: İki farklı iş parçacığı (thread) aynı anda çalışıyor.
start()
ile başlatılır,join()
ile tamamlanması beklenir.
join()
fonksiyonu olmazsa ne olur?
Program main thread’i bitmeden bitebilir. Çıktı sıralaması garanti edilemez.
Kısacası, join() metodu, başlattığınız thread’lerin görevlerini tamamladığından emin olmak ve ana programın onlardan önce bitmesini engellemek için kullanılır. Özellikle thread’lerin ürettiği sonuçlara ana thread’de ihtiyacınız varsa veya tüm işlemlerin bittiğinden emin olmak istiyorsanız join() kullanmak önemlidir.
Thread’ler aynı anda paylaşılan bir veriyi değiştirirse, beklenmedik sonuçlar ortaya çıkabilir. Buna “race condition” denir. Aşağıdaki örnekte, birden fazla thread aynı anda counter
değişkenini artırmakta ve sonuç her zaman doğru çıkmamaktadır.
threads = []
for _ in range(10):
thread = threading.Thread(target=worker)
threads.append(thread)
for thread in threads:
thread.start()
for thread in threads:
thread.join()
expected_value = len(threads) * 10000
print(f"Son sayaç değeri: {counter.counter}")
print(f"Beklenen sayaç değeri: {expected_value}")
import threading
import time
class Counter:
def __init__(self):
self.counter = 0
self.lock = threading.Lock() # Lock nesnesini başlat
def increment(self):
with self.lock: # Lock'u edin (context manager ile)
# Bu blok içindeki kod, aynı anda sadece bir thread tarafından çalıştırılabilir.
current_value = self.counter
# Race condition'ı göstermek için eklenen uyku, lock ile korunduğu için
# artık veri kaybına neden olmamalı, ancak işlemleri yavaşlatabilir.
# Çözümde bu satırı kaldırabiliriz veya bırakabiliriz.
# Genellikle gerçek bir çözümde bu tür yapay gecikmeler olmaz.
# Şimdilik, lock'un çalıştığını göstermesi için bırakalım.
time.sleep(0.00001)
self.counter = current_value + 1
# Lock bu bloktan çıkıldığında otomatik olarak serbest bırakılır.
...
Açıklama:
with self.lock
ifadesi sayesinde aynı anda sadece bir threadcounter
değerini değiştirebilir.
Açıklama:
with kilit:
sayesinde aynı anda sadece bir threadsayi
değişkenini değiştiriyor.
Multiprocessing, her biri kendi belleğine sahip olan farklı işlemler (process) ile çalışarak gerçek paralellik sağlar. Bu yöntem CPU-bound (yoğun) işlemlerde daha verimlidir.
import multiprocessing
def carp(x):
return x * x
if __name__ == '__main__':
with multiprocessing.Pool(processes=4) as pool:
sonuc = pool.map(carp, [1, 2, 3, 4, 5])
print(sonuc)
Açıklama:
map
fonksiyonu listedeki her bir eleman için farklı bir process çalıştırır.
1’den 100’e kadar sayıların karelerini hesaplayan bir program yazın. Her işlem, 10 sayının karelerini hesaplasın.
from multiprocessing import Process
def calculate_squares(start, end):
result = []
for i in range(start, end + 1):
result.append(i * i)
print(f"{start}-{end} arası kareler: {result}")
if __name__ == "__main__":
processes = []
for i in range(1, 101, 10):
p = Process(target=calculate_squares, args=(i, i + 9))
processes.append(p)
p.start()
for p in processes:
p.join()
print("Tüm işlemler bitti!")
Açıklama: Her işlem, 10’ar sayının karelerini hesaplar ve paralel çalışır.
asyncio, asenkron görevlerin aynı anda yürütülmesini sağlayan bir Python kütüphanesidir. await
ifadesiyle belirtilen işlemler beklenirken başka işler yapılabilir, bu da özellikle I/O-bound (yoğun) işlemler için avantaj sağlar.
import asyncio
async def selamla():
print("Merhaba")
await asyncio.sleep(1)
print("Naber?")
async def main():
await asyncio.gather(selamla(), selamla())
asyncio.run(main())
Açıklama: Aynı anda iki selamla fonksiyonu başlatılıyor.
sleep
sırasında diğeri çalışabiliyor.
Hangisi gerçek paralellik sağlar?