Thread Synchronization
Thread Synchronization
When multiple threads execute concurrently within the same process, they share a unified memory space. While this shared heap enables fast inter-thread communication without serialization, it introduces severe hazards: race conditions, memory corruption, and deadlocks.
The threading module provides synchronization primitives—Lock, RLock, Semaphore, Event, and Condition—to coordinate shared access safely.
1. The Mechanics of a Race Condition
Even a seemingly trivial operation such as counter += 1 is not atomic in Python bytecode:
Visual Architecture & Process Flow
How data and code flow step-by-step
2. Mutex Locks (threading.Lock)
A Mutex (Mutual Exclusion lock) ensures that only one thread can execute a critical section at any instant. Always acquire locks using the with statement to guarantee release:
3. Reentrant Locks (threading.RLock)
A standard threading.Lock cannot be acquired more than once by the same thread. If a method holding a lock calls another method that requests the same lock, execution deadlocks.
An RLock (Reentrant Lock) tracks the owning thread and an internal acquisition counter. The owning thread can acquire the lock multiple times without blocking, provided it releases it the same number of times:
4. Resource Throttling with threading.Semaphore
A Semaphore manages an internal counter. Every acquire() decrements the counter; every release() increments it. If the counter reaches zero, subsequent acquiring threads block. This is ideal for limiting concurrent access to rate-limited APIs or database connection pools:
Visual Architecture & Process Flow
How data and code flow step-by-step
5. Signaling with threading.Event
An Event manages an internal boolean flag (False by default). One thread can signal other threads to proceed by calling event.set(), while consumer threads pause via event.wait():
6. Architectural Summary Table
| Primitive | Mechanism | Primary Use Case |
|---|---|---|
Lock | Binary mutex (locked / unlocked) | Protecting critical sections and shared mutable data |
RLock | Reentrant mutex with recursion counter | Recursive function calls or nested class methods |
Semaphore | Counter-based permits | Throttling concurrency (connection pools, rate limits) |
Event | Boolean flag (wait / set / clear) | One-to-many thread signaling and coordination |
Condition | Lock associated with a wait queue | Complex producer-consumer pipelines |
Multiple Choice Questions
1.
Why does counter += 1 lead to race conditions in multithreaded Python despite the Global Interpreter Lock (GIL)? A. The GIL is disabled in loops. B. The += operation compiles down to multiple bytecode instructions (LOAD_FAST, BINARY_ADD, STORE_FAST), and thread switching can occur between them. C. Integers in Python are stored on disk. D. Hardware threads always ignore the GIL.
+= is not atomic at the bytecode level. The interpreter can switch threads after reading the variable but before writing the updated value back, resulting in lost updates.2.
What will happen if a thread that already holds a standard threading.Lock attempts to acquire that same lock a second time? A. The second attempt returns True immediately. B. The lock is released. C. The thread blocks waiting for itself to release the lock, causing a deadlock. D. A TypeError is raised.
threading.Lock is non-reentrant; if the owning thread attempts to re-acquire it, it blocks waiting for the lock to become free, permanently deadlocking itself.3.
Which synchronization primitive should be chosen when a single thread needs to acquire the same lock multiple times in recursive or nested function calls? A. threading.Lock B. threading.RLock C. threading.Event D. threading.Barrier
threading.RLock (Reentrant Lock) tracks the identity of the owning thread and its acquisition depth, allowing the owner to acquire it multiple times without blocking.4.
What is the primary function of a threading.Semaphore(value=5)? A. To guarantee that exactly 5 threads terminate at the same time. B. To allow up to 5 concurrent threads to hold the resource simultaneously before blocking additional requests. C. To create 5 separate memory heaps. D. To broadcast a stop signal to 5 worker threads.
5.
Which method on a threading.Event object is used by waiting worker threads to block until a signal flag is set to True? A. event.set() B. event.wait() C. event.listen() D. event.block()
event.wait() pauses the calling thread until the event's internal boolean flag is set to True via a call to event.set().Daemon vs Non-Daemon Threads
Continue learning with hands-on practice, examples, and exercises in the upcoming topic.
Related Lessons
| Previous Lesson | Next Lesson |
|---|---|
| Threading Module Basics | Daemon vs Non-Daemon Threads |
Practice Quiz
Test your understanding of this lesson with 5 questions. Each question has one correct answer.