Concurrency
Thread Synchronization
Locks, semaphores, conditions, and events — primitives for coordinating threads and preventing race conditions in shared state.
Interview: Thread safety is a top interview topic — know when and how to use each synchronization primitive.
Thread synchronization prevents race conditions — bugs that occur when multiple threads access shared data simultaneously, producing unpredictable results. Python's threading module provides several primitives to coordinate threads safely.
Lock (Mutex)
threading.Lock()— the simplest synchronization primitive- Only one thread can hold the lock at a time — others block until it's released
- Always use
with lock:(context manager) — guarantees release even on exceptions RLock()— reentrant lock, allows the same thread to acquire it multiple times
Semaphore
threading.Semaphore(n)— allows up to n threads to access a resource concurrently- Useful for rate limiting — controlling how many threads access a shared resource
BoundedSemaphore(n)— raises ValueError if released more times than acquired
Condition Variable
threading.Condition()— threads wait for a condition to be truewait()— releases the lock and blocks until notifiednotify()/notify_all()— wakes up waiting threads- Classic producer-consumer pattern uses Condition variables
Event
threading.Event()— a flag that threads can wait onevent.set()— sets the flag to True, waking all waiting threadsevent.wait()— blocks until the flag is set- Useful for signaling between threads (e.g., "initialization complete")
Barrier
threading.Barrier(n)— blocks until n threads all reach the barrier- All threads proceed together after the barrier is reached
- Useful for parallel algorithms where phases must complete together
Interview Insight
Know the difference: Lock (1 thread), Semaphore (N threads), Condition (wait for state change), Event (signal), Barrier (sync point). In Python, the GIL makes some operations atomic (list.append, dict assignment), but never rely on this — always use locks for compound operations.
Common Pitfall
Deadlocks happen when two locks are acquired in different orders by different threads. Always acquire locks in a consistent global order, or use RLock when a function might call itself recursively.
Use Cases
Shared counters/accumulators — Lock for atomic updates
Connection pooling — Semaphore to limit concurrent connections
Producer-consumer — Condition variables for thread-safe communication
Initialization barriers — Event to coordinate startup of multiple workers
Parallel algorithms — Barrier to synchronize computation phases
Common Mistakes
Forgetting to release locks — always use with statement (context manager)
Acquiring locks in inconsistent order — causes deadlocks between threads
Using Lock instead of RLock for recursive functions — thread deadlocks on itself
Busy-waiting (polling) instead of using Condition/Event — wastes CPU cycles
Not using BoundedSemaphore — accidental extra releases hide bugs