Introduction
Thread synchronization is essential in multithreaded programming to prevent race conditions and ensure data integrity when multiple threads access shared resources. Python provides several synchronization primitives in the threading
module, such as Locks, RLocks, Semaphores, Conditions, and Events.
Synchronization Primitives
1. Lock
A Lock
is the simplest form of synchronization primitive. It ensures that only one thread can access a resource at a time.
Example
import threading
lock = threading.Lock()
shared_resource = 0
def increment():
global shared_resource
for _ in range(1000000):
with lock:
shared_resource += 1
# Create threads
threads = [threading.Thread(target=increment) for _ in range(2)]
# Start threads
for thread in threads:
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
print(f"Final value of shared_resource: {shared_resource}")
Output
Final value of shared_resource: 2000000
2. RLock (Reentrant Lock)
An RLock
(Reentrant Lock) allows a thread to acquire the same lock multiple times without causing a deadlock.
Example
import threading
rlock = threading.RLock()
shared_resource = 0
def increment():
global shared_resource
for _ in range(1000000):
with rlock:
shared_resource += 1
# Create threads
threads = [threading.Thread(target=increment) for _ in range(2)]
# Start threads
for thread in threads:
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
print(f"Final value of shared_resource: {shared_resource}")
Output
Final value of shared_resource: 2000000
3. Semaphore
A Semaphore
controls access to a resource with a fixed number of permits.
Example
import threading
import time
semaphore = threading.Semaphore(2)
def access_resource(thread_id):
with semaphore:
print(f"Thread {thread_id} accessing resource")
time.sleep(2)
print(f"Thread {thread_id} releasing resource")
# Create threads
threads = [threading.Thread(target=access_resource, args=(i,)) for i in range(4)]
# Start threads
for thread in threads:
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
print("All threads have finished execution.")
Output
Thread 0 accessing resource
Thread 1 accessing resource
Thread 0 releasing resource
Thread 1 releasing resource
Thread 2 accessing resource
Thread 3 accessing resource
Thread 2 releasing resource
Thread 3 releasing resource
All threads have finished execution.
4. Condition
A Condition
allows threads to wait for certain conditions to be met before continuing execution. It is often used in conjunction with a Lock or RLock.
Example
import threading
condition = threading.Condition()
queue = []
def consumer():
with condition:
condition.wait() # Wait for the condition to be notified
while queue:
item = queue.pop(0)
print(f"Consumed: {item}")
def producer():
with condition:
for i in range(5):
queue.append(i)
print(f"Produced: {i}")
condition.notify_all() # Notify all waiting threads
# Create threads
consumer_thread = threading.Thread(target=consumer)
producer_thread = threading.Thread(target=producer)
# Start threads
consumer_thread.start()
producer_thread.start()
# Wait for all threads to complete
consumer_thread.join()
producer_thread.join()
print("All threads have finished execution.")
Output
Produced: 0
Produced: 1
Produced: 2
Produced: 3
Produced: 4
Consumed: 0
Consumed: 1
Consumed: 2
Consumed: 3
Consumed: 4
All threads have finished execution.
5. Event
An Event
is a simple synchronization primitive that allows one thread to signal one or more other threads that an event has occurred.
Example
import threading
import time
event = threading.Event()
def waiter():
print("Waiting for event to be set")
event.wait() # Wait for the event to be set
print("Event has been set!")
def setter():
time.sleep(2)
print("Setting the event")
event.set()
# Create threads
waiter_thread = threading.Thread(target=waiter)
setter_thread = threading.Thread(target=setter)
# Start threads
waiter_thread.start()
setter_thread.start()
# Wait for all threads to complete
waiter_thread.join()
setter_thread.join()
print("All threads have finished execution.")
Output
Waiting for event to be set
Setting the event
Event has been set!
All threads have finished execution.
Conclusion
Thread synchronization is crucial for managing concurrent access to shared resources and preventing race conditions in multithreaded programs. Python’s threading
module provides several synchronization primitives such as Locks, RLocks, Semaphores, Conditions, and Events to help you manage thread synchronization effectively. Understanding these primitives and how to use them will enable you to write robust and efficient multithreaded applications in Python.