Asynchronous I/O (asyncio)
Introduction
Async IO is a bit lesser known than its tried-and-true cousins, multiprocessing and threading:
-
Parallelism consists of performing multiple operations at the same time. Multiprocessing is a means to effect parallelism, and it entails spreading tasks over a computer’s central processing units (CPUs, or cores). Multiprocessing is well-suited for CPU-bound tasks: tightly bound for loops and mathematical computations usually fall into this category.
-
Concurrency is a slightly broader term than parallelism. It suggests that multiple tasks have the ability to run in an overlapping manner. (There’s a saying that concurrency does not imply parallelism.)
-
Threading is a concurrent execution model whereby multiple threads take turns executing tasks. One process can contain multiple threads.
Concurrency encompasses both multiprocessing (ideal for CPU-bound tasks) and threading (suited for IO-bound tasks). Multiprocessing is a form of parallelism, with parallelism being a specific type (subset) of concurrency. The Python standard library has offered longstanding support for both of these through its multiprocessing
, threading
, and concurrent.futures
packages.
The asyncio
module (introduced in Python 3.4) provides infrastructure for writing single-threaded concurrent code using coroutines, multiplexing I/O access over sockets and other resources, running network clients and servers, and other related primitives.
Async IO is not threading, nor is it multiprocessing. It is not built on top of either of these. Async IO is a single-threaded, single-process design: it uses cooperative multitasking. Coroutines (a central feature of async IO) can be scheduled concurrently, but they are not inherently concurrent.
- Asynchronous routines are able to "pause" while waiting on their ultimate result and let other routines run in the meantime.
- Asynchronous code, through the mechanism above, facilitates concurrent execution (gives the "look and feel" of concurrency).
Warning: Python’s async IO API has evolved rapidly from Python 3.4 to Python 3.7. Some old patterns are no longer used, and some things that were at first disallowed are now allowed through new introductions.
https://realpython.com/async-io-python/
Event loop
The event loop is the central execution device provided by asyncio
.
Coroutines
Coroutines declared with async
/await
syntax is specialized generator functions and the preferred way of writing asyncio applications. A coroutine is a function that can suspend its execution before reaching return, and it can indirectly pass control to another coroutine for some time.
Example
import asyncio
async def count():
print("One")
await asyncio.sleep(1)
print("Two")
async def main():
await asyncio.gather(count(), count(), count())
if __name__ == "__main__":
import time
s = time.perf_counter()
asyncio.run(main())
elapsed = time.perf_counter() - s
print(f"{__file__} executed in {elapsed:0.2f} seconds.")