Python Coroutines
1. Concept of Coroutines
A coroutine is a lightweight, user-space thread. Unlike OS threads, coroutines are managed in user space and are much lighter. Python implements coroutines using the async
/await
syntax.
Key characteristics:
- Non-blocking: Coroutines can pause themselves when waiting for I/O, allowing other coroutines to run.
- High concurrency in a single thread: Unlike multi-threading or multi-processing, coroutine context switches are extremely lightweight.
- Cooperative scheduling: Coroutines voluntarily yield control, rather than being preempted by the OS.
Simplified analogy:
- Threads: "compete for resources," OS decides which runs first.
- Coroutines: "take turns," tasks decide when to pause and resume.
2. Basics of Python Coroutines
2.1 Defining a Coroutine
Python 3.5+ uses async def
to define coroutine functions:
python
import asyncio
async def say_hello():
print("Hello")
await asyncio.sleep(1) # simulate a time-consuming task
print("World")
Here, await
suspends the coroutine until the asynchronous operation completes.
2.2 Running a Coroutine
A coroutine function does not execute immediately; it must be run via an event loop:
python
asyncio.run(say_hello())
The event loop schedules coroutine execution and manages suspension and resumption.
3. Coroutines vs Threads vs Synchronous I/O
Feature | Synchronous | Threads | Coroutines |
---|---|---|---|
Concurrency | Sequential | Parallel/Concurrent | Concurrent |
I/O Blocking | Blocks | Non-blocking | Non-blocking |
Overhead | Small | High | Very small |
Switching | N/A | OS scheduler | Cooperative |
Example: Download multiple web pages concurrently
- Synchronous:
python
def download(url):
# blocking I/O
pass
for url in urls:
download(url)
- Coroutine:
python
async def download(url):
await aiohttp.get(url) # non-blocking
Coroutines can perform other tasks while waiting for network requests, improving efficiency.
4. Core asyncio
Concepts
- Coroutine function: Defined with
async def
. - Task: Wraps a coroutine and schedules it in the event loop.
- Event loop: Manages execution of all coroutines.
- Future: Represents the eventual result of an asynchronous operation.
Example:
python
import asyncio
async def task(name, delay):
print(f"{name} starts")
await asyncio.sleep(delay)
print(f"{name} ends")
return f"{name} result"
async def main():
t1 = asyncio.create_task(task("A", 2))
t2 = asyncio.create_task(task("B", 1))
results = await asyncio.gather(t1, t2)
print(results)
asyncio.run(main())
Output sequence:
A starts
B starts
B ends
A ends
['A result', 'B result']
Explanation:
- Coroutines A and B run concurrently.
- B takes less time, so it finishes first.
5. Use Cases for Coroutines
- High-concurrency I/O-bound tasks: Web servers, web crawlers, network requests.
- Asynchronous operations: Database queries, file I/O, network communication.
- GUI and game loops: Prevent blocking the interface.
Note:
- Coroutines are not ideal for CPU-bound tasks; heavy computation should use threads or processes.
6. Summary
- Coroutines = lightweight user-space threads, paused and resumed using
await
. - Python coroutines are based on
asyncio
; the core is the event loop. - Coroutines excel in high-concurrency I/O scenarios.
- Mastering coroutines enables writing efficient and lightweight network programs.