Asynchronous Python: asyncio
One thread, thousands of overlapping I/O operations, no callbacks
asyncio runs many I/O-bound operations concurrently on a single thread. A coroutine runs until it hits an await on something not yet ready (a socket, a timer, a response); at that point it suspends and hands control back to the event loop, which runs another ready coroutine. Nothing runs in parallel on the CPU — instead, waiting time is overlapped. This is cooperative concurrency: tasks yield voluntarily at every await.
- explain the difference between concurrency and parallelism, and why asyncio targets I/O-bound work;
- write
async defcoroutines and drive them withasyncio.run; - schedule real concurrency with
asyncio.create_taskandasyncio.gather; - bound work in time with
asyncio.timeoutand handleCancelledErrorcorrectly; - consume streams with
async forand writeasyncgenerators; - recognise the traps: forgotten
await, blocking calls that freeze the loop, and CPU-bound misuse.
1. Concurrency is not parallelism
Parallelism means several things run at the same instant on different CPU cores (what multiprocessing gives you). Concurrency means several tasks are in progress and interleaved, but at any single instant only one is actually executing. asyncio provides concurrency, not parallelism: it uses one thread and overlaps the waiting.
This is exactly right for I/O-bound work — HTTP requests, database queries, reading files, talking to sockets — where your program spends most of its time waiting for something external. While one request waits for bytes to arrive, the loop runs another. For CPU-bound work (crunching numbers, parsing huge blobs) asyncio gives no speed-up at all, because there is no idle waiting to overlap; use processes or threads there instead.
await is a moment of "this needs time, let me do something else meanwhile".