When multiple threads access shared resources, proper synchronization is crucial to avoid inconsistent or unpredictable results. Python’s threading module provides several tools for synchronization and coordination among threads.
The most basic form of synchronization is the lock, provided by the Lock class in the threading module. A lock allows only one thread at a time to access a block of code or data.
import threading
# Create a lock
lock = threading.Lock()
def function_that_uses_lock():
with lock:
# Only one thread can execute this block at a time
print("Lock acquired. Critical section is running.")
# Start threads that access the function using the lock
thread1 = threading.Thread(target=function_that_uses_lock)
thread2 = threading.Thread(target=function_that_uses_lock)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
In this example, the with statement manages acquiring and releasing the lock, which ensures that the code block inside the with statement is executed by only one thread at a time.
Besides basic locks, the threading module also provides several other mechanisms for thread synchronization and coordination:
import threading
# Create a condition variable
condition = threading.Condition()
# A list used as a shared resource
queue = []
def producer():
with condition:
queue.append("Item produced")
print("Item added to queue")
condition.notify()
def consumer():
with condition:
while not queue:
condition.wait()
item = queue.pop(0)
print(f"Consumed {item}")
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)
consumer_thread.start()
producer_thread.start()
producer_thread.join()
consumer_thread.join()
This example demonstrates a simple producer-consumer scenario where the producer adds items to a queue and notifies the consumer, which waits for items to be produced.
While synchronization primitives are powerful, they come with challenges:
Queue which are easier to use and manage.