Python'da `threading` Modülü

Python'da `threading` Modülü

Bu makale, Python'daki threading modülünü açıklar.

YouTube Video

Python'da threading Modülü

Python'daki threading modülü, çok iş parçacıklı programlamayı destekleyen bir standart kütüphanedir. Thread'leri kullanmak, aynı anda birden fazla işlemin çalışmasına olanak tanır ve özellikle I/O beklemeleri gibi engelleyici işlemleri içeren durumlarda programların performansını artırabilir. Python'un Global Interpreter Lock (GIL) özelliği nedeniyle, çoklu thread'lerin etkinliği CPU odaklı işlemler için sınırlıdır, ancak I/O odaklı işlemler için verimli bir şekilde çalışır.

Aşağıdaki bölümler, threading modülünün kullanımının temellerini ve iş parçacıklarının nasıl kontrol edileceğini açıklar.

Thread'lerin Temel Kullanımı

Thread Oluşturma ve Çalıştırma

Bir thread oluşturmak ve eş zamanlı işlem yapmak için threading.Thread sınıfını kullanın. Bir thread oluşturmak ve çalıştırmak için hedef fonksiyonu belirtin.

 1import threading
 2import time
 3
 4# Function to be executed in a thread
 5def worker():
 6    print("Worker thread started")
 7    time.sleep(2)
 8    print("Worker thread finished")
 9
10# Create and start the thread
11thread = threading.Thread(target=worker)
12thread.start()
13
14# Processing in the main thread
15print("Main thread continues to run")
16
17# Wait for the thread to finish
18thread.join()
19print("Main thread finished")
  • Bu örnekte, worker fonksiyonu ayrı bir thread'de çalıştırılırken, ana thread işlemeye devam eder. join() metodunu çağırarak, ana thread alt thread'in tamamlanmasını bekler.

İş Parçacıklarına (Thread) İsim Verme

İş parçacıklarına anlamlı isimler vermek, günlükleme ve hata ayıklamayı kolaylaştırır. Bunu name argümanı ile belirtebilirsiniz.

 1import threading
 2import time
 3
 4# Function to be executed in a thread
 5def worker():
 6    print("Worker thread started")
 7    time.sleep(2)
 8    print("Worker thread finished")
 9
10t = threading.Thread(
11    target=worker,
12    args=("named-worker", 0.3),
13    name="MyWorkerThread"
14)
15
16t.start()
17
18print("Active threads:", threading.active_count())
19for th in threading.enumerate():
20    print(" -", th.name)
21
22t.join()
  • threading.enumerate(), mevcut iş parçacıklarının bir listesini döndürür; bu da hata ayıklama ve durum izleme için faydalıdır.

Thread Sınıfını Miras Alma

Thread çalıştıran sınıfı özelleştirmek istiyorsanız, threading.Thread sınıfını miras alarak yeni bir sınıf tanımlayabilirsiniz.

 1import threading
 2import time
 3
 4# Inherit from the Thread class
 5class WorkerThread(threading.Thread):
 6    def __init__(self, name, delay, repeat=3):
 7        super().__init__(name=name)
 8        self.delay = delay
 9        self.repeat = repeat
10        self.results = []
11
12    def run(self):
13        for i in range(self.repeat):
14            msg = f"{self.name} step {i+1}"
15            print(msg)
16            self.results.append(msg)
17            time.sleep(self.delay)
18
19# Create and start the threads
20t1 = WorkerThread("Worker-A", delay=0.4, repeat=3)
21t2 = WorkerThread("Worker-B", delay=0.2, repeat=5)
22
23t1.start()
24t2.start()
25
26t1.join()
27t2.join()
28
29print("Results A:", t1.results)
30print("Results B:", t2.results)
  • Bu örnekte run() metodu, iş parçacığının davranışını tanımlamak için geçersiz kılınıyor ve her iş parçacığının kendi verisini tutmasına olanak sağlanıyor. Bu, iş parçacıkları karmaşık işlemler yaptığında veya her birinin bağımsız verilere sahip olmasını istediğinizde kullanışlıdır.

Thread'ler Arasında Senkronizasyon

Birden fazla thread aynı anda ortak kaynaklara eriştiğinde, veri yarışları oluşabilir. Bunu önlemek için, threading modülü birkaç senkronizasyon mekanizması sağlar.

Kilit (Lock)

Lock nesnesi, thread'ler arasında kaynakların münhasır kontrolünü sağlamak için kullanılır. Bir thread bir kaynağı kilitlediğinde, diğer thread'ler o kaynağa erişemez.

 1import threading
 2
 3lock = threading.Lock()
 4shared_resource = 0
 5
 6def worker():
 7    global shared_resource
 8    with lock:  # Acquire the lock
 9        local_copy = shared_resource
10        local_copy += 1
11        shared_resource = local_copy
12
13threads = [threading.Thread(target=worker) for _ in range(5)]
14
15for t in threads:
16    t.start()
17
18for t in threads:
19    t.join()
20
21print(f"Final value of shared resource: {shared_resource}")
  • Bu örnekte, beş thread ortak bir kaynağa erişir, ancak Lock, birden fazla thread'in veriyi aynı anda değiştirmesini önlemek için kullanılır.

Yeniden Girişli Kilit (RLock)

Aynı iş parçacığının bir kilidi birden fazla kez edinmesi gerekiyorsa, bir RLock (yeniden girişli kilit) kullanın. Bu, özyinelemeli çağrılar veya farklı çağrılar sırasında kilit alabilen kütüphane fonksiyonları için kullanışlıdır.

 1import threading
 2
 3rlock = threading.RLock()
 4shared = []
 5
 6def outer():
 7    with rlock:
 8        shared.append("outer")
 9        inner()
10
11def inner():
12    with rlock:
13        shared.append("inner")
14
15t = threading.Thread(target=outer)
16t.start()
17t.join()
18print(shared)
  • RLock ile, aynı iş parçacığı zaten sahip olduğu bir kilidi yeniden alabilir; bu da iç içe geçmiş kilit edinimlerinde deadlock'u önlemeye yardımcı olur.

Koşul (Condition)

Condition, bir iş parçacığının belirli bir koşul karşılanana kadar beklemesini sağlamak için kullanılır. Bir iş parçacığı bir koşulu sağladığında, başka bir iş parçacığını uyarmak için notify() veya bekleyen tüm iş parçacıklarını uyarmak için notify_all() çağırabilirsiniz.

Aşağıda, bir Condition kullanan üretici ve tüketici örneği verilmiştir.

 1import threading
 2
 3condition = threading.Condition()
 4shared_data = []
 5
 6def producer():
 7    with condition:
 8        shared_data.append(1)
 9        print("Produced an item")
10        condition.notify()  # Notify the consumer
11
12def consumer():
13    with condition:
14        condition.wait()  # Wait until the condition is met
15
16        item = shared_data.pop(0)
17        print(f"Consumed an item: {item}")
18
19# Create the threads
20producer_thread = threading.Thread(target=producer)
21consumer_thread = threading.Thread(target=consumer)
22
23consumer_thread.start()
24producer_thread.start()
25
26producer_thread.join()
27consumer_thread.join()
  • Bu kodda, bir Condition kullanılarak veri eklendiğinde üretici, tüketiciyi bilgilendirir ve tüketici bu bildirimi bekleyip ardından veriyi alır; böylece senkronizasyon sağlanır.

İş Parçacıklarının Arka Plan Süreçlerine Dönüştürülmesi

Arka plan süreçleri, ana iş parçacığı sona erdiğinde zorla sonlandırılır. Normal iş parçacıkları sonlanmayı beklemek zorundayken, arka plan süreçleri otomatik olarak sonlanır.

 1import threading
 2import time
 3
 4def worker():
 5    while True:
 6        print("Working...")
 7        time.sleep(1)
 8
 9# Create a daemon thread
10thread = threading.Thread(target=worker)
11thread.daemon = True  # Set as a daemon thread
12
13thread.start()
14
15# Processing in the main thread
16time.sleep(3)
17print("Main thread finished")
  • Bu örnekte, worker iş parçacığı bir arka plan sürecine dönüştürülmüştür, bu nedenle ana iş parçacığı sona erdiğinde zorla sonlandırılır.

ThreadPoolExecutor ile İş Parçacığı Yönetimi

threading modülünün yanı sıra, concurrent.futures modülündeki ThreadPoolExecutor kullanılarak iş parçacığı havuzları yönetilebilir ve görevler paralel olarak yürütülebilir.

 1from concurrent.futures import ThreadPoolExecutor
 2import time
 3
 4def worker(seconds):
 5    print(f"Sleeping for {seconds} second(s)")
 6    time.sleep(seconds)
 7    return f"Finished sleeping for {seconds} second(s)"
 8
 9with ThreadPoolExecutor(max_workers=3) as executor:
10    futures = [executor.submit(worker, i) for i in range(1, 4)]
11    for future in futures:
12        print(future.result())
  • ThreadPoolExecutor, bir iş parçacığı havuzu oluşturur ve görevleri verimli bir şekilde işler. max_workers ile aynı anda çalışacak iş parçacığı sayısını belirtebilirsiniz.

İş Parçacıkları Arasında Olay İletişimi

threading.Event kullanarak, bir olayın gerçekleştiğini diğer iş parçacıklarına bildirmek için iş parçacıkları arasında bayraklar ayarlayabilirsiniz.

 1import threading
 2import time
 3
 4event = threading.Event()
 5
 6def worker():
 7    print("Waiting for event to be set")
 8    event.wait()  # Wait until the event is set
 9
10    print("Event received, continuing work")
11
12thread = threading.Thread(target=worker)
13thread.start()
14
15time.sleep(2)
16print("Setting the event")
17event.set()  # Set the event and notify the thread
  • Bu kod, işçi iş parçacığının Event sinyalini beklediği ve ana iş parçacığı event.set() çağırdığında işlemi sürdürdüğü bir mekanizmayı göstermektedir.

İş Parçacıklarında İstisna (Hata) Yönetimi ve Sonlandırma

İş parçacıklarında bir hata oluştuğunda, bu hatalar doğrudan ana iş parçacığına aktarılmaz; bu nedenle hataları yakalayıp paylaşmak için bir yapıya ihtiyaç vardır.

 1import threading
 2import queue
 3
 4def worker(err_q):
 5    try:
 6        raise ValueError("Something bad")
 7    except Exception as e:
 8        err_q.put(e)
 9
10q = queue.Queue()
11t = threading.Thread(target=worker, args=(q,))
12t.start()
13t.join()
14if not q.empty():
15    exc = q.get()
16    print("Worker raised:", exc)
  • Queue içine istisnaları koyarak ve ana iş parçacığında onları alarak hataları güvenilir bir şekilde tespit edebilirsiniz. concurrent.futures.ThreadPoolExecutor kullanırsanız, hatalar future.result() ile tekrar fırlatılır ve yönetilmesi kolaylaşır.

GIL (Global Interpreter Lock) ve Etkileri

CPython'daki GIL (Global Interpreter Lock) mekanizması nedeniyle, aynı işlemde birden fazla Python bayt kodu gerçekte eşzamanlı olarak çalışmaz. Ağır hesaplamalar gibi CPU yoğunluklu işler için multiprocessing kullanmanız önerilir. Öte yandan, dosya okuma veya ağ iletişimi gibi I/O ağırlıklı işler için threading etkili şekilde çalışır.

Özet

Python'un threading modülünü kullanarak, çok iş parçacıklı programlar oluşturabilir ve aynı anda birden fazla süreci çalıştırabilirsiniz. Lock ve Condition gibi senkronizasyon mekanizmalarıyla, paylaşılan kaynaklara güvenli bir şekilde erişebilir ve karmaşık senkronizasyon gerçekleştirebilirsiniz. Ayrıca, arka plan süreçleri veya ThreadPoolExecutor kullanarak, iş parçacığı yönetimi ve verimli paralel işlem yapmak daha kolay hale gelir.

Yukarıdaki makaleyi, YouTube kanalımızda Visual Studio Code'u kullanarak takip edebilirsiniz. Lütfen YouTube kanalını da kontrol edin.

YouTube Video