Advanced · Lesson 17 of 18 · 20 min

Async and concurrency

The GIL, threads, processes, and asyncio's event loop.

  • Choose threads vs processes vs asyncio
  • Write async def / await
  • Know what the GIL does and does not protect

Three kinds of waiting

Your program is either crunching numbers, waiting on I/O, or both. CPython has a Global Interpreter Lock: only one thread runs Python bytecode at a time. Threads still help for I/O (the GIL is released around blocking I/O and many C extensions). CPU-bound work wants `multiprocessing` or a native library that releases the GIL (NumPy).

`asyncio` is cooperative concurrency on one thread: tasks yield at `await`, and the event loop runs whoever is ready. It shines at thousands of sockets. It does not make a single CPU-bound function faster.

Pick a tool

ProblemToolWhy
Many network callsasyncio / trioOne thread, many sockets
Blocking I/O, simple codethreadsOS overlap, shared memory
CPU-bound PythonmultiprocessingBypasses the GIL
CPU-bound arraysNumPy / C extReleases the GIL
async def is a coroutine factory
import asyncio

async def fetch(name, delay):
    await asyncio.sleep(delay)
    return name

async def main():
    gathered = await asyncio.gather(
        fetch("a", 0.01),
        fetch("b", 0.02),
        fetch("c", 0.01),
    )
    return gathered

print(asyncio.run(main()))

The rules that keep async sane

`async def` returns a coroutine object. Nothing runs until you await it or schedule it. Forgetting to await is a silent bug (you'll get a warning). Never call blocking functions (`time.sleep`, sync HTTP) inside a running loop — use `asyncio.sleep` and async libraries, or `asyncio.to_thread`.

Threads share memory, so they need locks for writes. Processes do not share memory; you pass picklable messages. The GIL is not a substitute for your own locking around invariants.

Check yourself

Quiz

  1. 1.The GIL means:

  2. 2.Forgetting to await a coroutine typically:

  3. 3.CPU-bound pure Python is best scaled with:

Practice

Exercise

Write `async def add_after(a, b, delay)` that awaits asyncio.sleep(delay) then returns a + b. Write `async def add_many(pairs)` that concurrently adds every (a, b) pair with delay 0 and returns the list of sums in the same order.

Tutor

Ask a question about this lesson. Answers are generated on demand and capped so the session stays focused.