Concurrency vs. Parallelism
Concurrency lets multiple tasks make progress over overlapping time, even if they take turns on one execution resource. Parallelism is simultaneous work on multiple resources; in Python, the right approach depends on whether the workload is I/O-bound or CPU-bound.
Concurrency and parallelism are related but describe different properties of execution. A concurrent program structures multiple tasks so each can advance while others are waiting or otherwise not using the execution resource. A parallel program performs multiple parts of work at the same time, typically on different CPU cores.
A single-threaded event loop can provide concurrency: when one coroutine awaits network or timer activity, the loop can run another ready coroutine. This is useful for many I/O-bound operations, but it does not make CPU-heavy Python code run simultaneously on multiple cores.
In Python, threads can help when tasks spend time blocked on I/O. In standard GIL-enabled CPython, only one thread at a time executes Python bytecode, so threads usually do not provide CPU parallelism for pure Python work. The `multiprocessing` module uses separate processes, which can run on separate cores, at the cost of process startup, communication, and data-transfer overhead.
Concurrency can improve throughput when it keeps resources busy while tasks wait, but it does not guarantee a speedup. Parallelism can reduce elapsed time for suitable CPU-bound work, though coordination overhead, uneven task sizes, or limited hardware can erase the benefit. Choose based on the workload and measure performance.
Key points
- Concurrency allows tasks to make progress over overlapping periods; simultaneous execution is not required.
- Parallelism means multiple computations execute at the same time on distinct processing resources.
- `asyncio` is useful for coordinating many I/O-bound tasks, but CPU-heavy code can block its event loop.
- Threads can overlap I/O waits; standard GIL-enabled CPython threads generally do not parallelize pure Python bytecode.
- Processes can use multiple cores, but introduce startup and communication costs.
- Measure the workload: concurrency or parallelism can fail to improve throughput when another resource is the bottleneck.
Interview questions
What is the difference between concurrency and parallelism?
Concurrency is about structuring work so that multiple tasks can make progress during overlapping periods; they might take turns on one thread. Parallelism is actual simultaneous execution, usually using multiple cores. An asynchronous event loop can be concurrent without being parallel. Parallel execution is especially useful for CPU-bound work when splitting and coordinating the work costs less than the time saved.
How does asyncio provide concurrency?
`asyncio` schedules coroutines on an event loop, which runs a coroutine until it suspends, commonly at an `await`. While a coroutine waits for I/O or a timer, the loop can run another ready coroutine. This allows many I/O-bound tasks to share a thread efficiently. A CPU-heavy coroutine that does not yield can block the event loop and delay other tasks.
When would you choose threads versus processes in Python?
For blocking I/O, threads are often straightforward because a thread waiting on I/O can let another thread do useful work. For pure Python CPU-bound work under standard GIL-enabled CPython, threads generally do not execute Python bytecode in parallel, so a process pool is often a better starting point. Processes have additional startup, serialization, and communication costs. Benchmark the real workload and consider whether native libraries release the GIL or a free-threaded runtime is in use.
How can concurrency and parallelism affect a backend service's throughput?
Concurrency can increase throughput by allowing a service to start or handle other requests while some requests are waiting on network, database, or disk I/O. Parallelism can reduce the completion time of CPU-heavy work when that work can be divided across resources. Neither property alone guarantees better throughput: bottlenecks such as database capacity, locks, serialization, or CPU saturation may dominate. Measure throughput and latency under representative load, and check whether the bottleneck is I/O, CPU, or coordination.