Skip to content
AyoKoding

Overview

Prerequisites

  • Prior topics: 4 · Just Enough Python -- every script in this topic is fully type-annotated Python, and you should already be comfortable reading functions, classes, exceptions, context managers, and dict/list literals the way that primer taught them; 7 · Data Structures & Algorithms Essentials -- this topic leans on the queue and deque shapes you already built there for the producer/consumer and work-stealing examples; 22 · Programming Paradigms -- the "reduce shared mutable state" mindset that topic introduced through the functional lens is the same discipline this topic applies under real concurrent execution, where an unprotected mutable variable is not just harder to reason about, it is actively unsafe.
  • Tools & environment: a macOS/Linux terminal; Python 3.13.12 (stdlib threading, multiprocessing, asyncio, concurrent.futures, queue, dis, sys -- no third-party packages for examples 1-81); reactivex 4.1.0 (RxPY) for the two Observable-based reactive-streams examples (82-83) -- installed only into learning/code/ex-82-observable-map-filter-rxpy/ and learning/code/ex-83-hot-vs-cold-subscription/ via a per-example requirements.txt, since every other example in this topic is pure stdlib; pytest 9.1.1 to run each example's test file; pyright in --strict mode (pyrightconfig.json at the topic root) -- every example.py and test_example.py in this topic passes pyright --strict with zero errors. Optionally, a free-threaded python3.14t build (PEP 703/779) to see examples 4, 58, and 59 exercise true thread-level parallelism instead of the GIL-serialized behavior the standard build shows -- not required, since every free-threading example branches on sys._is_gil_enabled() and is genuinely correct on whichever interpreter actually runs it.
  • Assumed knowledge: writing Python functions, classes, and loops; running a script from the CLI; reading a stack trace; the general idea that two things running "at once" can interfere with each other, even without prior hands-on concurrency experience.

Why this exists -- the big idea

The problem before the solution: one thing at a time is simple but slow and unresponsive -- the moment two things run at once, shared state corrupts and bugs stop being reproducible. The one idea worth keeping if you forget everything else: don't share mutable state; when you must, protect it -- almost every concurrency bug in this topic, from a lost counter update to a five-way dining-philosophers deadlock, is a shared-state bug wearing a different costume.

Cross-cutting big ideas, taught here and then reused for the rest of this topic: taming-state -- the whole discipline of concurrent programming is containing the state two things can touch, whether through a lock, a single owner, or a message-passing queue. determinism-vs-emergence -- interleavings turn deterministic single-threaded code into emergent, order-dependent behavior, which is exactly why a race condition can pass every test run in isolation and still fail unpredictably under load (see the stress-harness example, ex-80). This topic covers the core concurrency model every engineer needs -- threads vs processes vs async, synchronization, races and deadlocks, message passing, and parallel decomposition -- in Python, including the GIL and free-threaded CPython, and closes with a six-example reactive-streams sub-group (Observables, hot/cold subscriptions, backpressure, and reactive pull) that applies the same shared-state and flow-control discipline to a push-based, operator-composed stream model.

Install and run your first example

Confirm the toolchain this topic's Beginner tier needs is installed:

$ python3 --version
Python 3.13.12
$ python3 -c "import threading, multiprocessing, asyncio, concurrent.futures, queue; print('stdlib concurrency primitives OK')"
stdlib concurrency primitives OK
$ python3 -m pytest --version
pytest 9.1.1

Every example is a complete, self-contained runnable file colocated under learning/code/ex-NN-slug/, paired with its own test_example.py, and both files are actually executed to capture the documented output -- never a fabricated transcript. Run any example directly:

cd learning/code/ex-01-concurrency-vs-parallelism-illustration
python3 example.py
pytest -q

Two examples (82 and 83) need one extra package -- install it inside that example's own directory only:

cd learning/code/ex-82-observable-map-filter-rxpy
pip install -r requirements.txt   # reactivex==4.1.0

A note on free-threaded CPython: examples 4, 58, and 59 discuss and exercise the free-threaded (python3.14t) build. PEP 779 made free-threading officially supported as of Python 3.14, but on macOS the installer's free-threaded option is still literally labeled "[experimental]" in the python.org installer UI -- supported by the PEP, but the platform installer's own copy has not yet caught up. All three examples are written to branch on sys._is_gil_enabled(), so they run correctly and honestly on whichever interpreter actually executes them, standard or free-threaded.

How this topic's examples are organized

  • Beginner (Examples 1-28) -- concurrency vs. parallelism, processes vs. threads, the GIL, threading.Thread start/join, the shared-counter race and its Lock fix, RLock, Semaphore, Event, Barrier, Condition, queue.Queue, a basic producer/consumer pipeline, ThreadPoolExecutor, ProcessPoolExecutor, Future, and your first asyncio coroutine and gather.
  • Intermediate (Examples 29-57) -- a reproduced two-lock deadlock and two different fixes (lock ordering, timeout), livelock, starvation, the four Coffman conditions, memory visibility, data races vs. logic races, bounded-queue backpressure, multi-producer/multi-consumer queues, a hand-built Condition-based bounded buffer, as_completed, exception propagation through a Future, multiprocessing IPC (Queue, Value), pool-vs-serial benchmarks for I/O and CPU work, asyncio.create_task/timeout/Queue/Semaphore, the cooperative-blocking hazard, offloading blocking calls with run_in_executor, Amdahl's Law, and a map-reduce decomposition.
  • Advanced (Examples 58-87) -- free-threaded CPython actually scaling CPU-bound threads, double-checked locking, a hand-built reader-writer lock, deadlock detection via a wait-for graph, a lock-free single-owner-queue counter, threading.local(), Future/Task cancellation, asyncio.wait, TaskGroup, a rate-limited concurrent fetch-and-aggregate, a three-stage pipeline, work-stealing, multiprocessing.shared_memory, asyncio.to_thread, the spurious-wakeup guard, phased-computation barriers, three-way I/O and CPU benchmarks, graceful shutdown, deadlock-free dining philosophers, a race-detecting stress harness, a capstone-preview benchmark, and a self-contained reactive-streams sub-group (Examples 82-87): an Observable with map/filter via reactivex (RxPY), hot vs. cold subscriptions, hand-rolled backpressure (buffer vs. latest -- RxPY 4.1.0 ships no built-in backpressure operators), reactive pull via request(n), a hand-rolled java.util.concurrent.Flow-style contract, and an annotated marble diagram of a merge + map + debounce pipeline.

The 33 concepts this topic covers

  • co-01 · Concurrency vs. parallelism -- concurrency is dealing with many things by interleaving; parallelism is doing many at once on multiple cores. Examples 1, 81.
  • co-02 · Processes vs. threads -- processes have isolated address spaces; threads share one, which is both cheaper and more dangerous. Examples 2, 45, 46.
  • co-03 · The GIL -- CPython's global interpreter lock serializes bytecode so only one thread runs Python at a time. Examples 3, 25, 49, 58, 77.
  • co-04 · Free-threaded CPython -- the PEP 703/779 no-GIL build (python3.14t, supported since 3.14) enables true thread parallelism. Examples 4, 58, 59.
  • co-05 · I/O-bound vs. CPU-bound -- the workload class decides the tool: threads/async win I/O, processes win CPU. Examples 3, 5, 23, 28, 48, 49, 59, 76, 77.
  • co-06 · Thread creation and join -- spawn a threading.Thread, start() it, join() to wait for completion. Examples 5, 6, 7.
  • co-07 · Shared-mutable-state hazard -- two threads touching one mutable variable is the root of nearly every concurrency bug. Examples 8, 64.
  • co-08 · Race condition -- a result that depends on the nondeterministic interleaving of operations. Examples 8, 10, 11, 37, 60, 80.
  • co-09 · Data race vs. race condition -- a data race is unsynchronized concurrent access; a race condition is the broader ordering bug. Example 37.
  • co-10 · Atomicity -- an operation that completes indivisibly; x += 1 is a non-atomic load-modify-store. Examples 9, 36.
  • co-11 · Locks and mutexes -- a threading.Lock making a critical section mutually exclusive. Examples 11, 12, 14, 31, 36, 47, 60, 61, 80.
  • co-12 · Reentrant locks -- a threading.RLock the owning thread can re-acquire without self-deadlock. Examples 13, 14.
  • co-13 · Semaphores -- a counter permitting at most N concurrent holders of a resource. Examples 15, 16, 53, 61, 69.
  • co-14 · Condition variables -- wait/notify coordination of threads around a shared predicate. Examples 19, 41, 74.
  • co-15 · Events and barriers -- a threading.Event one-shot signal and a Barrier N-way rendezvous. Examples 17, 18, 75.
  • co-16 · Deadlock -- a cyclic wait where each thread holds what another needs (the four Coffman conditions). Examples 29, 30, 31, 34, 62, 79.
  • co-17 · Livelock and starvation -- threads active yet making no progress, or one thread perpetually denied its turn. Examples 32, 33.
  • co-18 · Lock-ordering discipline -- acquiring locks in one global order to break the circular-wait condition. Examples 30, 62, 79.
  • co-19 · Memory visibility -- one thread's writes may not be observed by another without synchronization. Examples 35, 64, 72.
  • co-20 · Message passing over shared state -- "communicate by sharing queues, not by sharing memory". Examples 20, 46, 63.
  • co-21 · Thread-safe queues -- queue.Queue as a synchronized hand-off channel between threads. Examples 20, 21, 38, 40, 63, 70.
  • co-22 · Producer-consumer pattern -- decoupling producers from consumers through a bounded buffer. Examples 21, 22, 38, 39, 40, 41, 52, 70, 78.
  • co-23 · Thread pools -- concurrent.futures.ThreadPoolExecutor reusing a fixed set of worker threads. Examples 23, 24, 42, 43, 48, 55, 65, 73, 76, 78, 81.
  • co-24 · Process pools -- ProcessPoolExecutor running work in separate processes to sidestep the GIL. Examples 25, 44, 47, 57, 72, 77, 81.
  • co-25 · Futures and async results -- a Future is a placeholder for a result not yet computed. Examples 24, 26, 42, 43, 65.
  • co-26 · Async/await and the event loop -- asyncio single-threaded cooperative concurrency driven by an event loop. Examples 27, 28, 50, 51, 52, 53, 54, 66, 67, 68, 69, 76, 81.
  • co-27 · Cooperative vs. preemptive scheduling -- await yields voluntarily; OS threads are preempted involuntarily. Examples 54, 55, 67, 73.
  • co-28 · Parallel decomposition and Amdahl's Law -- splitting map-reduce work across workers; speedup is bounded by the serial fraction. Examples 56, 57, 71, 75.
  • co-29 · Reactive streams and backpressure -- the Reactive Streams spec governs async stream exchange across a boundary with non-blocking backpressure via four interfaces (Publisher/Subscriber/Subscription/Processor), adopted into the JDK as java.util.concurrent.Flow (Java 9). Examples 85, 86.
  • co-30 · Observables and operators -- a ReactiveX Observable is a push producer (onNext/onError/onCompleted) -- the dual of a pull Iterable -- composed by chainable operators (map/filter/take), with marble diagrams visualizing emissions over time. Examples 82, 87.
  • co-31 · Hot vs. cold streams -- a cold Observable emits only on subscription so every subscriber sees the whole sequence; a hot Observable emits regardless of subscribers, so late subscribers miss earlier items. Example 83.
  • co-32 · Backpressure strategies -- when a producer outpaces its consumer the stream applies buffer / drop / latest / error; the reactive-pull model inverts push by having the subscriber request(n) its demand so the source emits only what was asked for. Examples 84, 85.
  • co-33 · Reactive Manifesto vs. FRP -- the Reactive Manifesto (responsive / resilient / elastic / message-driven) is a manifesto, not a spec; Rx-style reactive streams are discrete-event, distinct from original FRP's continuous-time behaviors + discrete events (Elliott & Hudak, 1997). Example 87.

Examples by Level

Beginner (Examples 1-28)

Intermediate (Examples 29-57)

Advanced (Examples 58-87)


← Previous: 23 · Functional Programming Drilling · Next: Beginner Examples

Last updated July 16, 2026

Command Palette

Search for a command to run...