Praktische Thread-Synchronisierungstechniken
Nachdem wir die grundlegenden Konzepte der Synchronisierung von gemeinsam genutzten Daten in Python-Threads behandelt haben, wollen wir uns einige praktische Beispiele für die Verwendung der verschiedenen Synchronisierungsprimitive ansehen.
Verwendung von Locks
Locks sind die grundlegendsten Synchronisierungsprimitive in Python. Sie stellen sicher, dass nur ein Thread zu einem bestimmten Zeitpunkt auf einen kritischen Codeabschnitt zugreifen kann. Hier ist ein Beispiel für die Verwendung eines Locks zum Schutz eines gemeinsamen Zählers:
import threading
counter = 0
lock = threading.Lock()
def increment_counter():
global counter
for _ in range(1000000):
with lock:
counter += 1
threads = []
for _ in range(2):
t = threading.Thread(target=increment_counter)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Final counter value: {counter}")
In diesem Beispiel wird das lock
-Objekt verwendet, um sicherzustellen, dass nur ein Thread auf den kritischen Codeabschnitt zugreifen kann, in dem die counter
-Variable erhöht wird.
Verwendung von Semaphoren
Semaphore werden verwendet, um den Zugriff auf eine begrenzte Anzahl von Ressourcen zu steuern. Hier ist ein Beispiel für die Verwendung eines Semaphors zur Begrenzung der Anzahl gleichzeitiger Datenbankverbindungen:
import threading
import time
database_connections = 3
connection_semaphore = threading.Semaphore(database_connections)
def use_database():
with connection_semaphore:
print(f"{threading.current_thread().name} acquired a database connection.")
time.sleep(2) ## Simulating database operation
print(f"{threading.current_thread().name} released a database connection.")
threads = []
for _ in range(5):
t = threading.Thread(target=use_database)
threads.append(t)
t.start()
for t in threads:
t.join()
In diesem Beispiel wird der connection_semaphore
verwendet, um die Anzahl gleichzeitiger Datenbankverbindungen auf 3 zu begrenzen. Jeder Thread muss ein "Zulässigkeitszeichen" (permit) vom Semaphor erhalten, bevor er eine Datenbankverbindung verwenden kann.
Verwendung von Bedingungsvariablen (Condition Variables)
Bedingungsvariablen ermöglichen es Threads, auf das Eintreten bestimmter Bedingungen zu warten, bevor sie ihre Ausführung fortsetzen. Hier ist ein Beispiel für die Verwendung einer Bedingungsvariablen zur Koordination der Produktion und des Verbrauchs von Elementen in einer Warteschlange:
import threading
import time
queue = []
queue_size = 5
queue_condition = threading.Condition()
def producer():
with queue_condition:
while len(queue) == queue_size:
queue_condition.wait()
queue.append(1)
print(f"{threading.current_thread().name} produced an item. Queue size: {len(queue)}")
queue_condition.notify_all()
def consumer():
with queue_condition:
while not queue:
queue_condition.wait()
item = queue.pop(0)
print(f"{threading.current_thread().name} consumed an item. Queue size: {len(queue)}")
queue_condition.notify_all()
producer_threads = [threading.Thread(target=producer) for _ in range(2)]
consumer_threads = [threading.Thread(target=consumer) for _ in range(3)]
for t in producer_threads + consumer_threads:
t.start()
for t in producer_threads + consumer_threads:
t.join()
In diesem Beispiel wird die queue_condition
-Variable verwendet, um die Produktion und den Verbrauch von Elementen in einer Warteschlange zu koordinieren. Produzenten warten, bis die Warteschlange freien Platz hat, während Verbraucher warten, bis die Warteschlange Elemente enthält.
Diese Beispiele zeigen, wie Sie die verschiedenen Synchronisierungsprimitive, die von Python's threading
-Modul bereitgestellt werden, verwenden können, um gemeinsam genutzte Ressourcen effektiv zu verwalten und häufige Parallelitätsprobleme zu vermeiden.