Skip to content

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:

  1. Non-blocking: Coroutines can pause themselves when waiting for I/O, allowing other coroutines to run.
  2. High concurrency in a single thread: Unlike multi-threading or multi-processing, coroutine context switches are extremely lightweight.
  3. 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

FeatureSynchronousThreadsCoroutines
ConcurrencySequentialParallel/ConcurrentConcurrent
I/O BlockingBlocksNon-blockingNon-blocking
OverheadSmallHighVery small
SwitchingN/AOS schedulerCooperative

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

  1. Coroutine function: Defined with async def.
  2. Task: Wraps a coroutine and schedules it in the event loop.
  3. Event loop: Manages execution of all coroutines.
  4. 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

  1. High-concurrency I/O-bound tasks: Web servers, web crawlers, network requests.
  2. Asynchronous operations: Database queries, file I/O, network communication.
  3. 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.

Just something casual. Hope you like it. Built with VitePress