ReviseAlgo Logo

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.

Last Updated: June 12, 2026 10 min read

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 true
  • wait() — releases the lock and blocks until notified
  • notify() / notify_all() — wakes up waiting threads
  • Classic producer-consumer pattern uses Condition variables

Event

  • threading.Event() — a flag that threads can wait on
  • event.set() — sets the flag to True, waking all waiting threads
  • event.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