Concurrency and Asynchronous Programming in Python
Make Python do many things at once, correctly: threads, processes and asyncio, and the measurements that tell you which to use.
Advanced · Software Development · about 13 hours · 5 modules · certificate on completion
Taught by Vladimìr Novàk, Chaiyapòn Srisomboòn, AI Learning Companions.
About this course
Most programs spend their time waiting, for the network, the disk or a database, and others are held back by a single busy CPU core. This course teaches the three ways Python deals with both, and when to use each: threads, processes and asyncio. It starts with how the operating system runs processes and threads and what CPython's global interpreter lock (GIL) does, including the optional free-threaded build that arrived in Python 3.13. Then it works through threading and locks, thread and process pools from concurrent.futures, and multiprocessing for CPU-bound work. The second half is asyncio: the event loop, tasks, gather and TaskGroup, timeouts and cancellation, async context managers, queues and semaphores, and concurrent HTTP calls with httpx.
Every module has a lab with starter code, and checks that look at results rather than timings, so they pass on any machine: fix an overselling box office with a lock, spread a CPU-bound search over every core, build an async job runner with time limits, and write a polite, retrying HTTP client tested against a fake server. The last module shows how race conditions and deadlocks happen in each model and how to prevent them, and how to choose a model from measurements. The capstone brings it all together: an aggregator that crawls a slow, unreliable local API with asyncio and scores the results in a process pool. Afterwards, Advanced FastAPI and Production Deployment puts these ideas to work in a web service.
What you'll learn
- Explain how processes, threads and the GIL decide what runs in parallel in CPython, including Python 3.13's optional free-threaded build
- Write threaded code that stays correct under contention, with locks, queues and thread pools from concurrent.futures
- Speed up CPU-bound Python with process pools, keeping the cost of copying data under control
- Build asyncio programs with tasks, TaskGroup, timeouts, cancellation, async context managers and bounded concurrency
- Call HTTP APIs concurrently with httpx, with timeouts, retries and tests that need no network
- Find and prevent race conditions and deadlocks, and choose the right concurrency model for a workload
Skills you'll gain
Concurrency, Multithreading, Multiprocessing, asyncio, Async HTTP clients, Synchronisation and locking, Performance measurement, Testing concurrent code
Tools
Python 3, asyncio, concurrent.futures, multiprocessing, httpx, pytest
Before you start
Confident Python: functions, classes, exceptions, context managers, modules and virtual environments, as in Python Programming with PyCharm. Introduction to FastAPI and Modern Web Services uses async def at an introductory level; this course explains what happens underneath. You need Python 3.11 or later (3.13 recommended), any editor, and httpx and pytest installed in a virtual environment (pip install httpx pytest).
Modules
Processes, threads and the GIL
Why concurrency speeds some programs up and not others: I/O-bound and CPU-bound work, how processes and threads differ, and what CPython's global interpreter lock and the new free-threaded build mean for parallel Python.
- Concurrency, parallelism and where the time goes (12 min)
- Processes and threads (12 min)
- The GIL and the free-threaded build (14 min)
- Hands-on lab: Profile I/O-bound and CPU-bound work three ways
Threads: locks, queues and thread pools
Use threads safely: hand work between them with thread-safe queues, protect shared state with locks and the other synchronisation primitives, and let concurrent.futures manage a pool of threads and their results.
- Threads and thread-safe queues (12 min)
- Locks and other synchronisation primitives (14 min)
- Thread pools with concurrent.futures (13 min)
- Hands-on lab: Fix an overselling box office, then pool a slow service
Processes for CPU-bound work
Use every core for CPU-bound Python with multiprocessing and ProcessPoolExecutor: start methods and the main-module guard, process pools and chunking, and sharing data between processes without paying too much for copying.
- multiprocessing and start methods (13 min)
- Process pools: ProcessPoolExecutor and Pool (13 min)
- Sharing data between processes (12 min)
- Hands-on lab: Use every core for a CPU-bound search
asyncio: the event loop, tasks and cancellation
Write concurrent code in a single thread with asyncio: coroutines and the event loop, tasks with gather and TaskGroup, timeouts and cancellation done safely, and the async building blocks: context managers, iterators, queues and semaphores.
- Coroutines and the event loop (13 min)
- Tasks, gather and TaskGroup (14 min)
- Timeouts and cancellation (13 min)
- Async context managers, iterators, queues and semaphores (13 min)
- Hands-on lab: Build an async job runner with limits and timeouts
Async HTTP, correctness and choosing a model
Call HTTP APIs concurrently with httpx, find and prevent race conditions and deadlocks in each model, and choose between threads, processes and asyncio from what a workload needs.
- Async HTTP with httpx (14 min)
- Race conditions and deadlocks (14 min)
- Choosing and combining models (13 min)
- Hands-on lab: Fetch from a slow API politely, and test it
Project: Build a concurrent price aggregator
A purchasing team needs a daily report on everything a supplier sells: each product's name, mean recent price and a risk score. The supplier's API, which you run locally with supplier_server.py, is slow and unreliable: product ids come in pages, some requests fail with 503 until retried, some always fail with 500, and a few take far too long. The risk score (risk_score in scoring.py) is pure Python and CPU-heavy. Build aggregate.py, a command-line tool that walks the listing pages, fetches every product concurrently with httpx.AsyncClient within a concurrency limit, retries what's worth retrying with backoff and jitter, gives up on slow requests with timeouts, scores products in a ProcessPoolExecutor through loop.run_in_executor so the event loop never blocks, and writes report.csv sorted by id. Ctrl+C must stop it cleanly: cancel outstanding work, close the client and the pool, and write a partial report clearly marked as incomplete. Against the provided server, a correct run scores 192 products and reports 4 that failed with HTTP 500 and 4 that timed out.
Part of
The products this course names are trademarks of their owners, who haven't made or endorsed it.