Concurrency Overview
Concurrency means making progress on more than one task at the same time. In Python it is a practical necessity: most real programs spend a large fraction of their time waiting for I/O (networks, disks, databases) or for CPU-heavy number crunching, and running those tasks sequentially wastes the machine. This tutorial covers the three concurrency models Python offers: threads, processes, and the asyncio event loop, plus the standard library tools that sit on top of each model.
CPU-Bound vs I/O-Bound
The first question in any concurrency design is what the work is waiting on:
- CPU-bound tasks (numeric loops, image processing, parsing) keep the core busy. They benefit from running on multiple cores, which in Python means separate processes.
- I/O-bound tasks (HTTP calls, database queries, file reads) spend most of their time waiting. They benefit from switching to another task while the first one waits, which threads and asyncio both provide.
The Three Models in Python
| Model | Module | Shares | Bypasses GIL | Typical use |
|---|---|---|---|---|
| Threads | threading | memory of one process | No (CPython) | I/O concurrency, UI responsiveness |
| Processes | multiprocessing | nothing by default | Yes | CPU-bound parallelism |
| Async tasks | asyncio | memory of one thread | Yes (single thread) | High-volume I/O, servers, scraping |
Standard Library Map
import threading # threads, locks, queues (thread-safe)
import multiprocessing # processes, Pipe, Queue, shared values
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import asyncio # event loop, coroutines, tasks
import queue # thread-safe FIFO queues
Summary
Classify your workload first: CPU-bound points to processes, I/O-bound points to threads or asyncio. Everything in this tutorial is built on the standard library, and the same decision logic applies whether you are building a CLI tool, a web worker, or a scraper.