⚡ Concurrency & Parallelism
Locks, RLock & Semaphore
Protect shared state from data races.
Even with the GIL, non-atomic operations (x += 1, read-modify-write on a dict) can interleave. Use a Lock to make a critical section atomic.
import threading
balance = 0
lock = threading.Lock()
def deposit(n):
global balance
with lock: # always prefer the context manager
local = balance
balance = local + n # safe: no other thread sees a torn value
threads = [threading.Thread(target=deposit, args=(1,)) for _ in range(10_000)]
for t in threads: t.start()
for t in threads: t.join()
print(balance) # 10000
**Which primitive when**
Lock— mutual exclusion. Can't be re-acquired by the same thread.RLock— re-entrant; the same thread can acquire it multiple times (recursion, nested calls).Semaphore(n)— at mostnthreads inside at once. Great for rate limiting.Event— one-shot signal between threads (set()/wait()).Condition— wait until a predicate becomes true (producer/consumer w/o queue).Barrier(n)— block untilnthreads all arrive.
> 🧪 Concept-only — needs real OS threads.