Takip et

Python Multiprocessing: Paralel İşlem Gücüyle Performansı Artırma

Python Multiprocessing: Paralel İşlem Gücüyle Performansı Artırma Python, basitliği ve geniş kütüphane desteğiyle popüler bir programlam

Python Multiprocessing: Paralel İşlem Gücüyle Performansı Artırma

Python, basitliği ve geniş kütüphane desteğiyle popüler bir programlama dilidir. Ancak, CPU yoğun görevlerde tek çekirdekli performans sınırlarına takılabilir. Bu noktada, Python’ın multiprocessing modülü devreye girerek programların birden fazla CPU çekirdeğini aynı anda kullanarak paralel işlem yapmasını ve böylece performansı önemli ölçüde artırmasını sağlar. Bu makalede, Python multiprocessing modülünün temellerini, neden önemli olduğunu, ana bileşenlerini ve çeşitli kullanım örneklerini detaylı bir şekilde inceleyeceğiz.

Giriş: Neden Paralel İşlem?

Modern bilgisayarlar genellikle birden fazla işlemci çekirdeğine sahiptir. Ancak, varsayılan olarak Python programları tek bir çekirdek üzerinde çalışır. Bu durum, özellikle yoğun hesaplama gerektiren görevlerde (örneğin, büyük veri analizi, bilimsel simülasyonlar, görüntü işleme) programın potansiyel performansının altında kalmasına neden olur. Paralel işlem, bir görevi daha küçük, bağımsız parçalara bölerek bu parçaların eş zamanlı olarak farklı işlemci çekirdeklerinde çalıştırılması prensibine dayanır. Bu sayede, aynı iş daha kısa sürede tamamlanabilir.

Python’da paralellik denince akla genellikle iki ana kavram gelir:
* Çoklu İş Parçacığı (Multithreading): Aynı süreç içinde birden fazla iş parçacığı oluşturarak eş zamanlılık sağlar. Ancak Python’daki Global Interpreter Lock (GIL) nedeniyle, aynı anda yalnızca bir iş parçacığı Python bytecode’unu çalıştırabilir. Bu durum, CPU yoğun görevlerde multithreading’in gerçek paralellik sağlamasını engeller. Daha çok I/O (giriş/çıkış) yoğun görevler için uygundur.
* Çoklu Süreç (Multiprocessing): Her biri kendi bellek alanına ve Python yorumlayıcısına sahip birden fazla bağımsız süreç oluşturur. Bu sayede GIL kısıtlamasını aşarak birden fazla CPU çekirdeğini aynı anda kullanabilir ve gerçek paralel işlem gücü elde edebilir. CPU yoğun görevler için idealdir.

Bu makale, multiprocessing modülüne odaklanarak Python programlarınızda gerçek paralelliği nasıl uygulayabileceğinizi gösterecektir.

Multiprocessing Neden Önemlidir?

multiprocessing modülü, Python programlarının performansını artırmak için kritik bir araçtır, özellikle aşağıdaki senaryolarda:

CPU Yoğun Görevler

CPU yoğun görevler, işlemci üzerinde yoğun hesaplamalar gerektiren ve genellikle I/O beklemeyen görevlerdir (örneğin, matematiksel hesaplamalar, şifreleme/şifre çözme, veri sıkıştırma). Python’ın Global Interpreter Lock (GIL) kısıtlaması nedeniyle, threading modülü bu tür görevlerde gerçek paralellik sağlayamaz. Çünkü GIL, aynı anda yalnızca bir Python iş parçacığının yorumlayıcıyı kullanmasına izin verir. multiprocessing ise her sürece kendi Python yorumlayıcısını ve bellek alanını atayarak bu kısıtlamayı aşar. Bu, her sürecin farklı bir CPU çekirdeğinde bağımsız olarak çalışabileceği anlamına gelir.

Çok Çekirdekli İşlemcilerden Yararlanma

Günümüz bilgisayarlarının çoğu çok çekirdekli işlemcilere sahiptir. multiprocessing modülü, bu çekirdeklerin tüm potansiyelini kullanarak programlarınızın birden fazla işlemi aynı anda yürütmesini sağlar. Bu, özellikle büyük veri setlerinin işlenmesi, karmaşık algoritmaların çalıştırılması veya birden fazla bağımsız görevin eş zamanlı olarak yürütülmesi gereken durumlarda performansı katlayabilir.

Daha İyi Kaynak Kullanımı

Paralel işlem, sistem kaynaklarının daha verimli kullanılmasına olanak tanır. İşlemci çekirdekleri boşta beklemek yerine aktif olarak görevleri işler. Bu, genel sistem verimliliğini artırır ve programların daha hızlı yanıt vermesini sağlar.

Python’ın multiprocessing Modülü

multiprocessing modülü, süreç tabanlı paralellik için zengin bir API sunar. Bu modül, süreç oluşturma, süreçler arası iletişim (IPC), senkronizasyon ve süreç havuzları gibi temel bileşenleri içerir. Başlıca sınıfları ve fonksiyonları şunlardır:

* Process: Yeni bir süreç oluşturmak ve yönetmek için temel sınıf.
* Queue: Süreçler arası güvenli veri iletişimi için FIFO (İlk Giren İlk Çıkar) kuyruk.
* Pipe: İki süreç arasında tek veya çift yönlü iletişim için boru.
* Lock, Semaphore, Event: Süreçler arası senkronizasyon mekanizmaları.
* Value, Array: Süreçler arasında paylaşılan bellek alanları.
* Pool: Bir grup işçi sürecini yönetmek için bir havuz. Görevleri bu havuzdaki süreçlere dağıtarak daha kolay paralel işlem yapmayı sağlar.

Temel Multiprocessing Kavramları ve Process Sınıfı

multiprocessing modülünün kalbinde Process sınıfı yer alır. Bu sınıf, yeni bir işletim sistemi süreci oluşturmanıza ve bu süreç içinde belirli bir fonksiyonu çalıştırmanıza olanak tanır.

Bir Süreç Oluşturma ve Yönetme

Bir Process nesnesi oluşturmak için genellikle target parametresi ile çalıştırılacak fonksiyonu ve args parametresi ile bu fonksiyona geçirilecek argümanları belirtirsiniz.

import multiprocessing
import os
import time

def worker_function(name):
    """Her sürecin çalıştıracağı fonksiyon."""
    print(f"Süreç {name} başladı. PID: {os.getpid()}")
    time.sleep(2) # Görevi simüle etmek için 2 saniye bekler
    print(f"Süreç {name} bitti.")

if __name__ == '__main__':
    print(f"Ana süreç başladı. PID: {os.getpid()}")

    # Bir Process nesnesi oluşturma
    process1 = multiprocessing.Process(target=worker_function, args=("Worker-1",))
    process2 = multiprocessing.Process(target=worker_function, args=("Worker-2",))

    # Süreçleri başlatma
    process1.start()
    process2.start()

    # Süreçlerin bitmesini bekleme
    # join() metodu, ana sürecin alt sürecin tamamlanmasını beklemesini sağlar.
    process1.join()
    process2.join()

    print("Ana süreç tüm alt süreçlerin bitmesini bekledi ve tamamlandı.")

Açıklama:
* multiprocessing.Process(target=worker_function, args=("Worker-1",)): Yeni bir süreç oluşturur. target parametresi, yeni sürecin çalıştıracağı fonksiyonu belirtir. args parametresi ise target fonksiyona argüman olarak geçirilecek bir demet (tuple) alır.
* process.start(): Süreci başlatır. Bu noktada worker_function yeni bir işletim sistemi sürecinde çalışmaya başlar.
* process.join(): Ana sürecin, ilgili alt sürecin tamamlanmasını beklemesini sağlar. Eğer join() çağrılmazsa, ana süreç alt süreçler henüz bitmeden kendi işine devam edebilir ve hatta sonlanabilir.
* if __name__ == '__main__':: Bu blok kritik öneme sahiptir. Özellikle Windows işletim sisteminde, alt süreçler ana modülü yeniden içe aktarır. Bu blok, alt süreçlerin multiprocessing.Process() çağrısını tekrar yürütmesini engelleyerek sonsuz bir süreç döngüsüne girmesini önler. Unix tabanlı sistemlerde fork başlangıç yöntemi kullanıldığında bu o kadar kritik değildir, ancak taşınabilirlik için her zaman kullanılması önerilir.

Süreçler Arası İletişim (Inter-Process Communication – IPC)

Süreçler kendi ayrı bellek alanlarına sahip oldukları için doğrudan birbirlerinin verilerine erişemezler. Bu nedenle, süreçler arasında veri paylaşımı veya iletişim kurmak için özel mekanizmalara ihtiyaç duyulur. multiprocessing modülü, bu amaçla çeşitli IPC araçları sunar.

Kuyruklar (Queue)

Queue, süreçler arası güvenli veri iletişimi için en yaygın kullanılan araçlardan biridir. FIFO (First-In, First-Out) prensibine göre çalışır ve birden fazla üreticinin veri koyup birden fazla tüketicinin veri alabileceği bir yapı sağlar.

import multiprocessing
import time
import random

def producer(queue):
    """Veri üreten süreç."""
    for i in range(5):
        item = f"Veri-{i}"
        print(f"Üretici: {item} ekliyor.")
        queue.put(item)
        time.sleep(random.uniform(0.1, 0.5))
    queue.put(None) # Tüketiciye işin bittiğini bildirmek için sentinel değeri

def consumer(queue):
    """Veriyi tüketen süreç."""
    while True:
        item = queue.get()
        if item is None: # Sentinel değeri alındığında döngüden çık
            break
        print(f"Tüketici: {item} aldı.")
        time.sleep(random.uniform(0.5, 1.0))
    print("Tüketici: İşlem tamamlandı.")

if __name__ == '__main__':
    my_queue = multiprocessing.Queue()

    p1 = multiprocessing.Process(target=producer, args=(my_queue,))
    c1 = multiprocessing.Process(target=consumer, args=(my_queue,))

    p1.start()
    c1.start()

    p1.join()
    c1.join()

    print("Ana süreç: Tüm işlemler bitti.")

Açıklama:
* multiprocessing.Queue(): Yeni bir kuyruk nesnesi oluşturur.
* queue.put(item): Öğeyi kuyruğa ekler.
* queue.get(): Kuyruktan bir öğe alır. Kuyruk boşsa, yeni bir öğe gelene kadar bekler.
* None sentinel değeri: Üretici, işi bittiğinde kuyruğa özel bir None değeri ekleyerek tüketicinin de işinin bittiğini anlamasını sağlar.

Borular (Pipe)

Pipe, iki süreç arasında tek veya çift yönlü iletişim kurmak için kullanılır. Bir boru, bir uçtan veri gönderip diğer uçtan almak için iki bağlantı nesnesi döndürür.

import multiprocessing

def sender_process(conn):
    """Veri gönderen süreç."""
    message = "Merhaba Boru!"
    print(f"Gönderici: '{message}' gönderiyor.")
    conn.send(message)
    conn.close()

def receiver_process(conn):
    """Veri alan süreç."""
    message = conn.recv()
    print(f"Alıcı: '{message}' aldı.")
    conn.close()

if __name__ == '__main__':
    parent_conn, child_conn = multiprocessing.Pipe()

    p_sender = multiprocessing.Process(target=sender_process, args=(child_conn,))
    p_receiver = multiprocessing.Process(target=receiver_process, args=(parent_conn,))

    p_sender.start()
    p_receiver.start()

    p_sender.join()
    p_receiver.join()

    print("Ana süreç: Boru iletişimi tamamlandı.")

Açıklama:
* multiprocessing.Pipe(): İki bağlantı nesnesi döndürür: parent_conn ve child_conn. Bu nesneler, borunun iki ucunu temsil eder.
* conn.send(data): Bağlantı üzerinden veri gönderir.
* conn.recv(): Bağlantıdan veri alır. Veri yoksa, veri gelene kadar bekler.
* conn.close(): Bağlantıyı kapatır.

Paylaşılan Bellek (Value ve Array)

Value ve Array sınıfları, süreçler arasında basit Python veri tiplerini (sayılar, karakterler) veya C tipi dizileri doğrudan bellekte paylaşmak için kullanılır. Ancak, paylaşılan belleğe erişimde senkronizasyon sorunları (yarış koşulları) ortaya çıkabilir, bu yüzden genellikle Lock ile birlikte kullanılırlar.

import multiprocessing
import time

def increment_counter(counter, lock):
    """Paylaşılan sayacı artıran süreç."""
    for _ in range(100000):
        # Kilidi al
        lock.acquire()
        try:
            counter.value += 1
        finally:
            # Kilidi serbest bırak
            lock.release()

if __name__ == '__main__':
    # 'i' tipi (integer) ve başlangıç değeri 0 olan paylaşılan bir Value oluştur
    shared_counter = multiprocessing.Value('i', 0)
    # Paylaşılan kaynağa erişimi senkronize etmek için bir kilit oluştur
    counter_lock = multiprocessing.Lock()

    processes = []
    for i in range(5):
        p = multiprocessing.Process(target=increment_counter, args=(shared_counter, counter_lock))
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

    print(f"Beklenen sonuç: 500000")
    print(f"Gerçek sonuç: {shared_counter.value}")

Açıklama:
* multiprocessing.Value('i', 0): ‘i’ C tipi bir işaretsiz tamsayıyı (signed integer) temsil eder ve başlangıç değeri 0’dır. Diğer C tipleri için ‘d’ (double), ‘c’ (char) vb. kullanılabilir.
* multiprocessing.Lock(): Bir kilit nesnesi oluşturur.
* lock.acquire(): Kilidi alır. Başka bir süreç kilidi tutuyorsa, bu çağrı kilit serbest bırakılana kadar engellenir.
* lock.release(): Kilidi serbest bırakır.
* try...finally: acquire() sonrası her durumda release() çağrılmasını garanti eder, böylece kilitin takılı kalması önlenir.
* Bu örnekte, Lock kullanılmasaydı shared_counter.value değeri muhtemelen 500000’den daha az olurdu, çünkü yarış koşulları nedeniyle bazı artırma işlemleri kaybolurdu.

Süreç Havuzu (Pool) ile Kolay Yönetim

Birçok durumda, belirli sayıda işçi sürecini yönetmek ve onlara görevler dağıtmak istersiniz. multiprocessing.Pool sınıfı, bu tür senaryolar için yüksek seviyeli bir arayüz sağlar. Bir havuz, belirli sayıda işçi sürecini önceden oluşturur ve bu süreçleri görevleri işlemek için kullanır.

Pool Kullanımının Avantajları

* Otomatik Süreç Yönetimi: Süreç oluşturma, sonlandırma ve yeniden kullanma gibi detayları otomatik olarak yönetir.
* Görev Dağıtımı: Görevleri mevcut işçi süreçlerine otomatik olarak dağıtır.
* Basit API: map, apply, apply_async gibi kullanımı kolay metodlar sunar.

import multiprocessing
import time

def expensive_function(x):
    """Yoğun hesaplama yapan bir fonksiyon."""
    time.sleep(0.1) # İşlem süresini simüle eder
    return x * x

if __name__ == '__main__':
    data = range(10) # İşlenecek veriler
    num_processes = 4 # Kullanılacak işlemci çekirdeği sayısı

    print(f"Ana süreç: {num_processes} çekirdekli bir havuz oluşturuluyor.")

    # Bir süreç havuzu oluşturma
    # processes parametresi belirtilmezse, os.cpu_count() kullanılır.
    with multiprocessing.Pool(processes=num_processes) as pool:
        # map() metodu: Bir fonksiyonu bir iterable üzerindeki tüm öğelere paralel olarak uygular.
        # Sonuçlar, orijinal iterable'daki sırayla döndürülür.
        results = pool.map(expensive_function, data)

    print(f"Ana süreç: Hesaplama sonuçları: {results}")

    print("\nAsenkron görev dağıtımı örneği (apply_async):")

    # apply_async ile asenkron görev dağıtımı
    with multiprocessing.Pool(processes=num_processes) as pool:
        async_results = []
        for item in data:
            # apply_async, görevi havuza gönderir ve bir AsyncResult nesnesi döndürür.
            # Ana süreç, alt süreçlerin bitmesini beklemeden devam edebilir.
            async_results.append(pool.apply_async(expensive_function, (item,)))

        # Tüm asenkron görevlerin sonuçlarını toplama
        final_results = [res.get() for res in async_results]

    print(f"Ana süreç: Asenkron hesaplama sonuçları: {final_results}")

Açıklama:
* multiprocessing.Pool(processes=num_processes): Belirtilen sayıda işçi sürecini içeren bir havuz oluşturur. with ifadesi, havuzun işi bittiğinde otomatik olarak kapatılmasını ve süreçlerin sonlandırılmasını sağlar.
* pool.map(function, iterable): function‘ı iterable‘daki her öğeye paralel olarak uygular. Sonuçları, giriş sırasına göre bir liste olarak döndürür. Bu bir bloklama çağrısıdır, yani tüm görevler bitene kadar ana süreç bekler.
* pool.apply_async(function, args): function‘ı args ile havuza gönderir ve hemen bir AsyncResult nesnesi döndürür. Bu, ana sürecin alt süreçlerin tamamlanmasını beklemeden başka işler yapmasına olanak tanır.
* res.get(): AsyncResult nesnesinden görevin sonucunu alır. Görev henüz tamamlanmadıysa, tamamlanana kadar bekler.

Senkronizasyon Mekanizmaları

Birden fazla sürecin aynı paylaşılan kaynağa (örneğin, paylaşılan bellek, dosya) eş zamanlı olarak erişmeye çalışması durumunda “yarış koşulları” (race conditions) ortaya çıkabilir. Bu, beklenmeyen veya yanlış sonuçlara yol açabilir. Senkronizasyon mekanizmaları, bu tür sorunları önlemek ve süreçlerin paylaşılan kaynaklara düzenli bir şekilde erişmesini sağlamak için kullanılır.

Kilitler (Lock)

Lock (kilit), bir kaynağa aynı anda yalnızca bir sürecin erişmesine izin veren en temel senkronizasyon ilkelidir. Bir süreç kaynağı kullanmak istediğinde kilidi alır; işi bittiğinde kilidi serbest bırakır.

import multiprocessing
import time

def safe_increment(shared_value, lock):
    for _ in range(100000):
        with lock: # Kilidi almak ve iş bitince otomatik serbest bırakmak için 'with' kullanılır.
            shared_value.value += 1

if __name__ == '__main__':
    counter = multiprocessing.Value('i', 0)
    lock = multiprocessing.Lock()

    processes = []
    for _ in range(5):
        p = multiprocessing.Process(target=safe_increment, args=(counter, lock))
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

    print(f"Beklenen değer: 500000")
    print(f"Son değer: {counter.value}")

Açıklama:
* with lock: ifadesi, lock.acquire() ve lock.release() çağrılarını otomatik olarak yöneten bir bağlam yöneticisidir. Bu, kilidin her zaman doğru bir şekilde serbest bırakılmasını sağlar, hata oluşsa bile.

Semaphorlar (Semaphore)

Bir semafor, belirli bir kaynağa aynı anda erişebilecek süreç sayısını sınırlamak için kullanılır. Örneğin, bir veritabanı bağlantı havuzuna aynı anda sadece belirli sayıda sürecin erişmesine izin vermek isteyebilirsiniz.

import multiprocessing
import time
import random

def worker_with_semaphore(semaphore, worker_id):
    print(f"İşçi {worker_id}: Kaynağa erişim izni bekliyor.")
    with semaphore: # Semaforu al (izin varsa devam et, yoksa bekle)
        print(f"İşçi {worker_id}: Kaynağa erişti ve çalışıyor.")
        time.sleep(random.uniform(1, 3)) # Kaynağı kullanma süresi
        print(f"İşçi {worker_id}: Kaynağı serbest bıraktı.")

if __name__ == '__main__':
    # Aynı anda 2 sürecin kaynağa erişmesine izin veren bir semafor
    resource_semaphore = multiprocessing.Semaphore(2)
    processes = []

    for i in range(5):
        p = multiprocessing.Process(target=worker_with_semaphore, args=(resource_semaphore, i + 1))
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

    print("Ana süreç: Tüm işçiler tamamlandı.")

Açıklama:
* multiprocessing.Semaphore(2): Aynı anda en fazla 2 sürecin with semaphore: bloğuna girmesine izin veren bir semafor oluşturur.

Olaylar (Event)

Bir Event (olay), bir sürecin diğer süreçlere bir olayın gerçekleştiğini bildirmesi için kullanılır. Bir veya daha fazla süreç, bir olayın gerçekleşmesini bekleyebilir.

import multiprocessing
import time

def waiter_process(event, process_id):
    print(f"Bekleyici {process_id}: Olayın gerçekleşmesini bekliyor...")
    event.wait() # Olayın ayarlanmasını bekler
    print(f"Bekleyici {process_id}: Olay gerçekleşti! Devam ediyor.")
    time.sleep(1)
    print(f"Bekleyici {process_id}: İşlem tamamlandı.")

def signaler_process(event):
    print("Sinyalci: 3 saniye sonra olayı ayarlayacak.")
    time.sleep(3)
    event.set() # Olayı ayarlar, bekleyen süreçleri uyandırır
    print("Sinyalci: Olay ayarlandı.")

if __name__ == '__main__':
    event = multiprocessing.Event()
    waiters = []

    for i in range(3):
        p = multiprocessing.Process(target=waiter_process, args=(event, i + 1))
        waiters.append(p)
        p.start()

    signaler = multiprocessing.Process(target=signaler_process, args=(event,))
    signaler.start()

    signaler.join()
    for p in waiters:
        p.join()

    print("Ana süreç: Tüm süreçler tamamlandı.")

Açıklama:
* multiprocessing.Event(): Yeni bir olay nesnesi oluşturur. Başlangıçta olay “clear” (temiz) durumdadır.
* event.wait(): Olay “set” (ayarlandı) durumuna gelene kadar bekler.
* event.set(): Olayı “set” durumuna getirir, wait() çağrısı yapan tüm süreçleri uyandırır.
* event.clear(): Olayı tekrar “clear” durumuna getirir.

Multiprocessing Kullanım Senaryoları

multiprocessing modülü, çeşitli gerçek dünya uygulamalarında performansı artırmak için kullanılabilir:

* Büyük Veri İşleme ve Analizi: Çok büyük veri setlerini paralel olarak okuma, dönüştürme ve analiz etme.
* Web Kazıma (Web Scraping): Birden fazla web sayfasını veya API çağrısını aynı anda yaparak veri toplama hızını artırma.
* Görüntü ve Video İşleme: Görüntü filtreleme, yeniden boyutlandırma veya video karelerini paralel olarak işleme.
* Bilimsel Hesaplamalar ve Simülasyonlar: Karmaşık matematiksel modelleri veya simülasyonları paralel olarak çalıştırma.
* Paralel Test Çalıştırma: Yazılım testlerini birden fazla süreçte aynı anda yürüterek test süresini kısaltma.
* Makine Öğrenimi: Model eğitimi veya hiperparametre optimizasyonu gibi yoğun görevleri paralel hale getirme.

Multiprocessing Kullanırken Dikkat Edilmesi Gerekenler

multiprocessing güçlü bir araç olsa da, kullanımında bazı önemli noktalar göz önünde bulundurulmalıdır:

* Süreç Oluşturma Maliyeti (Overhead): Yeni bir süreç oluşturmak, bir iş parçacığı oluşturmaktan daha maliyetlidir. Her süreç kendi bellek alanına ve yorumlayıcısına sahip olduğu için daha fazla sistem kaynağı tüketir. Bu nedenle, çok kısa süren veya çok fazla süreç oluşturan görevler için multiprocessing uygun olmayabilir.
* Veri Serileştirme (Serialization): Süreçler arası iletişimde (Queue, Pipe vb.), veriler bir süreçten diğerine gönderilirken serileştirilir (pickle). Bu serileştirme ve seri durumdan çıkarma işlemi de ek bir maliyet getirir. Büyük veya karmaşık nesnelerin sık sık süreçler arasında aktarılması performans düşüşüne neden olabilir.
* Bellek Tüketimi: Her süreç kendi bellek alanına sahip olduğundan, çok sayıda süreç oluşturmak sistem belleğini hızla tüketebilir. Bu durum, özellikle bellek kısıtlı sistemlerde sorunlara yol açabilir.
* Hata Ayıklama (Debugging): Paralel programlarda hata ayıklama, tek iş parçacıklı programlara göre daha zordur. Yarış koşulları, kilitlenmeler (deadlock) ve senkronizasyon sorunları gibi problemlerin tespiti ve çözümü karmaşıktır.
* Başlatma Yöntemleri (start_method): multiprocessing modülü, süreçleri başlatmak için farklı yöntemler sunar:
* 'fork' (Unix varsayılanı): Ana sürecin bellek alanının bir kopyasını oluşturur. En hızlıdır ancak bazen ana süreçteki açık dosya tanımlayıcıları veya kilitler gibi kaynakları alt süreçlere taşır.
* 'spawn' (Windows varsayılanı, macOS’ta önerilen): Tamamen yeni ve boş bir Python yorumlayıcı süreci başlatır. Ana süreçteki kaynakları doğrudan kopyalamaz, bu yüzden daha güvenli ve taşınabilirdir ancak daha yavaştır.
* 'forkserver' (Unix): Bir sunucu süreci başlatır ve bu sunucu süreci yeni alt süreçleri çatallandırır.
Kullanılan işletim sistemine ve uygulamanın gereksinimlerine göre doğru başlatma yöntemini seçmek önemlidir. multiprocessing.set_start_method() fonksiyonu ile bu yöntem değiştirilebilir.
* Ana Süreç Koruma (if __name__ == '__main__':): Daha önce belirtildiği gibi, bu blok özellikle spawn başlatma yöntemini kullanan veya Windows’ta çalışan uygulamalar için hayati öneme sahiptir. Süreçlerin sonsuz bir döngüye girmesini engeller.

Multiprocessing vs. Multithreading: Kısa Bir Karşılaştırma

| Özellik | Multiprocessing | Multithreading |
| :—————- | :———————————————– | :———————————————– |
| GIL Etkisi | GIL kısıtlamasını aşar; gerçek CPU paralelliği. | GIL nedeniyle CPU yoğun görevlerde gerçek paralellik sağlamaz. |
| Bellek Paylaşımı | Her sürecin kendi ayrı bellek alanı vardır; IPC mekanizmaları gereklidir. | Aynı süreç içinde bellek paylaşımı kolaydır. |
| Görev Tipi | CPU yoğun görevler için idealdir. | I/O yoğun görevler (ağ, disk erişimi) için daha uygundur. |
| Süreç Oluşturma | Daha ağır ve maliyetlidir. | Daha hafif ve daha hızlıdır. |
| Güvenlik/İzolasyon | Süreçler birbirinden izole olduğundan daha güvenlidir. | Aynı bellek alanını paylaştığından daha fazla senkronizasyon gerektirir. |
| Hata Ayıklama | Daha zordur. | Multiprocessing’e göre daha kolaydır. |

Sonuç

Python’ın multiprocessing modülü, CPU yoğun görevlerde performans darboğazlarını aşmak ve modern çok çekirdekli işlemcilerin gücünden tam olarak yararlanmak için vazgeçilmez bir araçtır. Process sınıfı ile temel süreç oluşturmadan, Queue ve Pipe ile süreçler arası güvenli iletişime, Pool ile işçi süreçlerini kolayca yönetmeye ve Lock, Semaphore, Event gibi senkronizasyon mekanizmalarıyla paylaşılan kaynaklara güvenli erişim sağlamaya kadar geniş bir yelpazede yetenekler sunar.

Her ne kadar süreç oluşturma maliyeti ve hata ayıklama zorlukları gibi bazı dezavantajları olsa da, doğru kullanıldığında multiprocessing, Python uygulamalarınızın hızını ve verimliliğini önemli ölçüde artırabilir. Büyük veri işleme, bilimsel hesaplamalar ve paralel testler gibi alanlarda Python geliştiricileri için kritik bir beceridir. Paralel programlamanın prensiplerini anlamak ve multiprocessing modülünü etkin bir şekilde kullanmak, daha performanslı ve ölçeklenebilir Python uygulamaları geliştirmenize olanak tanır.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Exit mobile version