Sharing state between threads safely

Concurrency in Python services Junior Python 3.13+ PostgreSQL 17+ Reviewed By Best Latest Apps

Fill the blank

Make the shared counter update safe across threads.

Python 3.13
import threading

counts: dict[str, int] = {}
lock = threading.Lock()


def hit(key: str) -> None:
    with ____:
        counts[key] = counts.get(key, 0) + 1


ts = [threading.Thread(target=hit, args=("a",)) for _ in range(50)]
for t in ts:
    t.start()
for t in ts:
    t.join()
print(counts)

Options
Hint 1 of 1

Every thread must use the same lock.

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A lock

Correct. The read, add and write happen while holding the lock.

counts.get(...) + 1 is a read, an add and a write, and another thread can run between them. Every thread has to hold the same shared lock for the whole read-modify-write so updates can't overlap.

Why not the others?
  • B threading.Lock(). A new lock each call protects nothing.
  • C counts. A dict isn't a context manager.
  • D threading.Event(). Events signal; they don't give mutual exclusion.

30-second answer

Treat shared mutable state as unsafe unless one operation is guaranteed atomic. d[k] += 1 is a read, an add and a write, and another thread can run between them. Use a threading.Lock around compound updates, prefer passing messages through queue.Queue (which is thread-safe), or keep state per thread and merge at the end. Better still, keep per-request state local and shared state in a store with atomic operations.

Practise saying it · 30 s. No recording, just a timer

Share state between threads.

Say your answer out loud, then check which of these points you covered:

  • Compound ops race
  • One shared Lock
  • Queues instead of sharing
  • Don't hold locks over I/O

Deep dive

Free-threaded Python makes these races more frequent, not new.

collections.Counter.update isn't atomic either.

Locks need a consistent order when you hold more than one.

Immutable data can be shared freely.

Common mistakes

  • Relying on the GIL for correctness.
  • Holding a lock while doing I/O.
  • Module-level caches mutated by many request threads.

Senior take & follow-ups

Compound operations race; use locks, queues or per-thread state; atomic stores across processes.

Follow-up questions

Think of your answer first, then open the hint.

  1. Is list.append thread-safe in CPython?

    Show hint

    A single append is atomic in practice, but don't build correctness on compound sequences.

  2. Lock or queue?

    Show hint

    Queues avoid sharing; locks protect what must be shared.

Suggest a fix if something here is wrong or out of date.