Techniques pratiques de synchronisation de threads
Maintenant que nous avons couvert les concepts de base de la synchronisation des données partagées dans les threads Python, explorons quelques exemples pratiques d'utilisation des différentes primitives de synchronisation.
Utilisation des verrous (Locks)
Les verrous sont la primitive de synchronisation la plus basique en Python. Ils garantissent qu'un seul thread peut accéder à une section critique de code à la fois. Voici un exemple d'utilisation d'un verrou pour protéger un compteur partagé :
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}")
Dans cet exemple, l'objet lock
est utilisé pour s'assurer qu'un seul thread peut accéder à la section critique de code où la variable counter
est incrémentée.
Utilisation des sémaphores (Semaphores)
Les sémaphores sont utilisés pour contrôler l'accès à un nombre limité de ressources. Voici un exemple d'utilisation d'un sémaphore pour limiter le nombre de connexions simultanées à une base de données :
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()
Dans cet exemple, le connection_semaphore
est utilisé pour limiter le nombre de connexions simultanées à la base de données à 3. Chaque thread doit obtenir un « permis » du sémaphore avant de pouvoir utiliser une connexion à la base de données.
Utilisation des variables de condition (Condition Variables)
Les variables de condition permettent aux threads d'attendre que certaines conditions soient remplies avant de poursuivre leur exécution. Voici un exemple d'utilisation d'une variable de condition pour coordonner la production et la consommation d'éléments dans une file d'attente :
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()
Dans cet exemple, la variable queue_condition
est utilisée pour coordonner la production et la consommation d'éléments dans une file d'attente. Les producteurs attendent que de l'espace soit disponible dans la file d'attente, tandis que les consommateurs attendent qu'il y ait des éléments dans la file d'attente.
Ces exemples démontrent comment vous pouvez utiliser les différentes primitives de synchronisation fournies par le module threading
de Python pour gérer efficacement les ressources partagées et éviter les problèmes courants de concurrence.