Reviewed 6 September 2026

Part II: Practices

Concurrency

Model Applies to
asyncio I/O-bound work with many concurrent operations: HTTP clients, database drivers, message consumers.
Threads (GIL builds) Blocking I/O and calls into C extensions that release the GIL.
Processes (multiprocessing, ProcessPoolExecutor) CPU-bound work; separate memory spaces, data passed by pickling.
Subinterpreters (concurrent.interpreters, PEP 734, 3.14) CPU-bound work with isolated state per interpreter and lower overhead than processes.
Free-threaded build (PEP 779, 3.14) CPU-bound work in threads with shared memory. Requires extensions built for it; an incompatible extension re-enables the GIL.

Structured concurrency in asyncio:

async with asyncio.timeout(30):
    async with asyncio.TaskGroup() as tg:
        a = tg.create_task(fetch(url_a))
        b = tg.create_task(fetch(url_b))

TaskGroup cancels remaining tasks when one fails and does not exit until all have finished, which asyncio.gather does not guarantee. asyncio.to_thread offloads blocking calls. anyio provides an alternative API that runs on both asyncio and Trio, and is used by libraries that must not assume a runtime.

Free-threading status: the build is officially supported from 3.14, and PEP 779 puts its single-threaded overhead at around 10% against the GIL build on the pyperformance suite, or about 3% on macOS. Wheel availability across the ecosystem is still incomplete. PEP 803 in 3.15 introduces abi3t, a stable ABI allowing one extension wheel to serve multiple free-threaded versions.

An extension that was not built for free-threading re-enables the GIL when it is imported. PYTHON_GIL=0, or -X gil=0, overrides that and keeps the GIL off, which runs the extension under exactly the conditions it declared it could not handle. The failures are data races inside C code, so they arrive as wrong results or a crash rather than an exception.

Subinterpreters carry the constraint from the other side. An extension must use multi-phase initialisation (PEP 489) and keep its state out of C globals to be imported into a second interpreter. NumPy implements the first and not the second, and raises ImportError in a subinterpreter, which rules out most of the compiled scientific stack. Ecosystem support is the fact that decides whether the model is usable at all, and it is currently minimal.

References