Lesson 15 of 34 · 0 of 1 exercises solved
Async & Concurrency
Many programs spend most of their time waiting on the network, the disk or a . asyncio lets one thread run many of those waits at the same time, so ten one-second take about one second instead of ten.
import asyncio, time
async def fetch(name, seconds):
print(f"start {name}")
await asyncio.sleep(seconds) # yields control while waiting
print(f"done {name}")
return f"{name}-result"
async def sequential():
a = await fetch("a", 1)
b = await fetch("b", 1)
return [a, b] # takes ~2 seconds
async def concurrent():
return await asyncio.gather(
fetch("a", 1),
fetch("b", 1),
) # takes ~1 second
start = time.perf_counter()
print(asyncio.run(concurrent()))
print(f"{time.perf_counter() - start:.2f}s")
import asyncio
# Tasks run in the background as soon as they are created
async def worker(n):
await asyncio.sleep(n * 0.1)
return n * n
async def main():
# TaskGroup (3.11+) — the modern way; cancels siblings if one fails
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(worker(i)) for i in range(5)]
print([t.result() for t in tasks]) # [0, 1, 4, 9, 16]
# Timeouts
try:
await asyncio.wait_for(asyncio.sleep(10), timeout=0.5)
except asyncio.TimeoutError:
print("gave up waiting")
# Bounding concurrency so you do not open 5000 sockets at once
sem = asyncio.Semaphore(10)
async def limited(i):
async with sem:
return await worker(i)
results = await asyncio.gather(*(limited(i) for i in range(100)))
print(len(results))
asyncio.run(main())
import asyncio
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
# Three kinds of concurrency, three right answers:
#
# asyncio -> many I/O waits, one thread (network, sockets, DB)
# threads -> blocking I/O in libraries that are not async-aware
# processes -> CPU-bound work (bypasses the GIL)
def cpu_heavy(n):
return sum(i * i for i in range(n))
def blocking_io():
import time; time.sleep(1); return "io done"
async def main():
loop = asyncio.get_running_loop()
# Push blocking work off the event loop so it stays responsive
with ThreadPoolExecutor() as pool:
print(await loop.run_in_executor(pool, blocking_io))
with ProcessPoolExecutor() as pool:
results = await asyncio.gather(*(
loop.run_in_executor(pool, cpu_heavy, 1_000_000)
for _ in range(4)
))
print(results[0])
asyncio.run(main())
Common question
Does asyncio make my program run in parallel?
Concurrency is dealing with many things at once; parallelism is doing many things at once. asyncio gives you the first, on a single thread, through an event that runs one task until it awaits and then switches to another task that is ready. So the program does useful work while I/O is in flight, which for network-heavy programs is most of the time.
Write coroutines with async , pause them only with await on things that can be awaited, and start many at once with gather or TaskGroup. Never call blocking functions inside the loop; wrap them with asyncio.to_thread if you have to. For CPU-heavy work, use multiprocessing or a native extension, because awaiting never makes arithmetic faster.
Source: The asyncio documentation: "asyncio is a to write concurrent code using the async/await syntax." Concurrent, not parallel.
Common mix-ups
Each of these sounds reasonable. Think about why it is wrong, then open the explanation.
asyncio.gather runs several coroutines at once, so it must be using several CPU cores.
Adding async to a function makes it faster.
time.sleep inside a coroutine pauses only that coroutine.
Practice
Write an async function fetch(name, delay) that sleeps for delay seconds with asyncio.sleep and returns name. In main(), run three fetches concurrently with asyncio.gather so the whole program finishes in about 0.3 seconds, not 0.6, and store the returned names in results.
Run tasks concurrently
import asyncio
import time
async def fetch(name, delay):
...
async def main():
global results
start = time.perf_counter()
# run fetch("A", 0.3), fetch("B", 0.2), fetch("C", 0.1) concurrently
results = []
print(results, f"{time.perf_counter() - start:.2f}s")
asyncio.run(main())
Press Run to try your code, then Check answer to grade it.Check yourself
What does await do inside an async function?
Tip: use the ← and → keys to move between lessons.