As Python developers, we often need to write programs that perform multiple tasks concurrently for high performance and throughput. This introduces the question - is asyncio truly concurrent or does it run tasks in parallel?
Let's explore the difference between concurrency and parallelism and how asyncio allows concurrency in Python.
Concurrency vs Parallelism
Concurrency refers to the ability to deal with lots of things at once. A concurrent system can handle multiple tasks by switching between them.
Parallelism is the ability to literally run multiple tasks at the exact same time, e.g. on multi-core CPUs.
The key difference is that concurrent systems handle multiple tasks by switching between them quickly, while parallel systems run tasks simultaneously.
How asyncio Works
The asyncio module introduced concurrency in Python 3 using a single-threaded, non-blocking approach. Here's a quick overview:
So asyncio enables concurrency, not parallelism. It runs multiple tasks on a single thread by switching context cooperatively.
Achieving High Performance
The fact that asyncio doesn't utilize multiple cores may seem like a disadvantage. However, properly written asyncio code can rival parallel solutions in performance. Here's why:
Avoid Slow I/O Bound Work
Asyncio shines when dealing with high latency I/O bound work like network calls and disk access. By switching tasks instead of blocking threads, you prevent wasting cycles waiting.
Scale with Less Resources
An asyncio server can handle thousands of connections on a single thread and process. Parallel solutions require more memory and threads to scale.
Use asyncio and Multiprocessing
For CPU heavy tasks, you can delegate work to process pools. This provides parallelism when needed while still using asyncio for I/O.
Here's an example:
import asyncio
from multiprocessing import Pool
async def cpu_heavy(n):
return pow(2, n)
def main():
pool = Pool()
loop = asyncio.get_event_loop()
futures = [loop.run_in_executor(pool, cpu_heavy, 2**i)
for i in range(20)]
loop.run_until_complete(asyncio.wait(futures))
for future in futures:
print(future.result())
main()
This runs multiple CPU heavy tasks in a process pool while the main thread uses asyncio coordination.
When to Use Asyncio
Here are some examples of workloads where asyncio shines:
Any I/O heavy workload will benefit from asyncio's cooperative multitasking approach.
Common Pitfalls
Here are some things to avoid when writing asyncio code:
Properly structured asyncio code avoids these issues and unlocks excellent performance.
Key Takeaways
So while asyncio isn't parallel, you can certainly achieve high throughput by mastering this performant single-threaded concurrency framework.
I hope this overview helps explain the difference between concurrency and parallelism in Python.