Python topluluğundaki en kalıcı efsanelerden biri, dilin doğası gereği yavaş olmasıdır. Standart CPython'da baytkodların gerçek paralel yürütülmesini engelleyen Küresel Yorumlayıcı Kilidi (GIL) olsa da, yüksek iş hacimli, G/Ç odaklı veya CPU odaklı iş yüklerini verimli bir şekilde yönetmekten uzaktır. Anahtar, eşzamanlılık ile paralellik arasındaki ayrımı anlamakta ve iş için doğru aracı seçmektir.
Orta seviyeden ileri seviyeye geliştiriciler için bu kavramları ustalaşmak, sadece daha hızlı kod yazmakla ilgili değildir; ölçeklenebilir sistemler mimarisi ile de ilgilidir. Bu yazı, Python'un eşzamanlılık modellerinin inceliklerini keşfetmekte ve paralel işlemeden yararlanmak için pratik stratejiler sunmaktadır.
Eşzamanlılık vs. Paralellik: Terimleri Tanımlamak
Koda dalmadan önce temel bir ayrımı netleştirmemiz gerekir. Eşzamanlılık, aynı anda birçok şeyle uğraşmakla ilgilidir. Yapı ile ilgilidir. Eşzamanlı bir sistemde görevler, örtüşen zaman dilimlerinde ilerleme kaydeder, ancak aynı anda yürütülme yapmayabilirler. Tek çekirdekli bir CPU'nun birden fazla tarayıcı sekmesini yönetmesini düşünün; aralarında o kadar hızlı bir geçiş yapar ki, eşzamanlıymış gibi görünür.
Böylece, paralellik, aynı anda birçok şey yapmakla ilgilidir. Yürütme ile ilgilidir. Paralel bir sistemde görevler, birden fazla işlemcide veya çekirdekte kelimenin tam anlamıyla aynı anda çalışır. Bu, donanım desteği (birden fazla çekirdek) ve bu çekirdekler arasında işi bölebilen bir programlama modeli gerektirir.
Threading: G/Ç Odaklı Şampiyon
Python'un threading modülü, eşzamanlılık için geleneksel yaklaşımdır. Ancak GIL nedeniyle, iş parçacıkları CPU odaklı görevler için uygun değildir. Ağır matematiksel hesaplamalar yapmak için beş iş parçacığı başlatırsanız, her an yalnızca bir iş parçacığı GIL'i tutabileceğinden, büyük olasılıkla sırayla, biri diğerinden sonra yürütüleceklerdir.
Bununla birlikte, G/Ç odaklı görevlere (ağ istekleri, dosya okuma veya veritabanı sorguları gibi) geldiğinde, iş parçacıkları son derece etkilidir. Bir iş parçacığı G/Ç beklerken GIL'i serbest bırakır ve diğer iş parçacıklarının çalışmasına izin verir. Bu da threading'i, yüksek iş hacimli ağ sunucuları veya tarayıcılar oluşturmak için ideal kılar.
İşte birden fazla web sayfasını eşzamanlı olarak indirmek için concurrent.futures kullanan basit bir örnek:
import concurrent.futures
import urllib.request
URLS = ['http://www.foxnews.com/', 'http://www.cnn.com/', 'http://espn.com/']
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout=timeout) as conn:
return conn.read()
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
print('%r page is %d bytes' % (url, len(data)))
Multiprocessing: CPU Görevleri İçin GIL'i Aşmak
Görüntü işleme, veri analizi veya karmaşık simülasyonlar gibi CPU odaklı görevler için, threading performans artışı sağlamaz. Gerçek paralellik elde etmek için çoklu işlemler kullanmanız gerekir. multiprocessing modülü, her işlem için Python yorumlayıcısının yerel bir kopyasını sağlar ve GIL'i tamamen aşar.
Bu, gerçek paralel yürütülme sağlar, ancak bunun bir bedeli vardır: işlem arası iletişim (IPC), iş parçacığı senkronizasyonundan daha pahalıdır ve yeni işlemler oluşturma bellek yükü daha yüksektir. Yine de, ağır hesaplama iş yükleri için, modern çok çekirdekli CPU'ları doyurmanın çoğu zaman tek yoludur.
from multiprocessing import Pool
def square(n):
return n * n
if __name__ == '__main__':
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
with Pool(4) as p:
results = p.map(square, numbers)
print(results)
Asyncio: G/Ç Eşzamanlılığı İçin Modern Standart
Son yıllarda, asyncio, Python'da yüksek eşzamanlı G/Ç'yi yönetmek için standart haline geldi. İşletim sistemi tarafından yönetilen bağlam değiştirmeye dayanan iş parçacıklarının aksine, asyncio, işbirlikçi çoklu görevi yönetmek için tek iş parçacıklı bir olay döngüsü kullanır. Bu yaklaşım son derece hafif ve verimlidir; bu da onu FastAPI ve Django Channels gibi modern web çerçevelerinin omurgası yapar.
Asyncio, senkron görünürken yüksek performans koruyan, senkron olmayan kod yazmanıza olanak tanır.
import asyncio
import aiohttp
async def fetch_url(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in ['http://example.com', 'http://python.org']]
results = await asyncio.gather(*tasks)
print(results)
asyncio.run(main())
Sonuç
Python'da doğru eşzamanlılık modelini seçmek, en "iyi" olanı seçmek değil, belirli darboğazınıza uyanı seçmekle ilgilidir. Basit, engellemeli G/Ç görevleri için iş parçacıkları kullanın. Ağır hesaplamalar için CPU kullanımını en üst düzeye çıkarmak istediğinizde çoklu işlemler kullanın. Ölçeklenebilir, yüksek iş hacimli senkron olmayan uygulamalar oluştururken asyncio kullanın.
Bu araçları ve bunların getirdiği ödünleşimleri anlayarak, sadece doğru değil, aynı zamanda performanslı ve ölçeklenebilir Python uygulamaları yazabilirsiniz. GIL'in potansiyelinizi sınırlamasına izin vermeyin; bugün paralellik için mimari yapmaya başlayın.