Backend Development 18 min read

Understanding Python Coroutines: From IO Multiplexing to Generators and Async/Await

This article explains how Python implements coroutines for high‑performance network and web programming by combining OS‑level IO multiplexing, generator‑based control flow, callback elimination, stack‑driven call‑chain traversal, Future objects, and the evolution toward async/await syntax.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Understanding Python Coroutines: From IO Multiplexing to Generators and Async/Await

The article begins by stating that modern Python web frameworks such as tornado now support the async and await keywords, and the author revisits the underlying principles of coroutine implementation.

It first introduces IO multiplexing as the key performance technique, showing a simplified server loop where a single thread repeatedly accepts requests, registers IO operations, and processes them without blocking:

def handler(request):
    # 处理请求
    pass

while True:
    # 获取一个新请求
    request = accept()
    # 根据路由映射获取到用户写的业务逻辑函数
    handler = get_handler(request)
    # 运行用户的handler,处理请求
    handler(request)

The article then explains how traditional multithreaded servers solve blocking but incur high overhead, while IO multiplexing lets the OS notify the program when network IO is ready.

A pseudo‑code example of the OS‑level IO multiplexing API is provided:

# 操作系统的IO复用示例伪代码
# 向操作系统IO注册自己关注的IO操作的id和类型
io_register(io_id, io_type)
io_register(io_id, io_type)

# 获取完成的IO操作
events = io_get_finished()

for (io_id, io_type) in events:
    if io_type == READ:
        data = read_data(io_id)
    elif io_type == WRITE:
        write_data(io_id, data)

To integrate this logic into a server, the author shows a callback‑based design where each request handler registers a callback and the event loop drives the callbacks:

call_backs = {}

def handler(req):
    # do jobs here
    io_register(io_id, io_type)
    def call_back(result):
        # 使用返回的result完成剩余工作...
        pass
    call_backs[io_id] = call_back

while True:
    # 获取已经完成的io事件
    events = io_get_finished()
    for (io_id, io_type) in events:
        if io_type == READ:
            data = read(io_id)
            call_back = call_backs[io_id]
            call_back(data)
        else:
            # 其他类型io事件的处理
            pass

    # 获取一个新请求
    request = accept()
    handler = get_handler(request)
    handler(request)

Because callbacks split business logic, the article proposes using generators to turn callbacks into linear code. A handler can now yield an IO description and receive the result when the coroutine is resumed:

def handler(request):
    # 业务逻辑代码...
    # 需要执行一次 API 请求
    result = yield io_info
    # 使用 API 返回的result完成剩余工作
    print(result)

An example of a simple generator demonstrates the send() method and the StopIteration exception carrying the return value:

def example():
    value = yield 2
    print("get", value)
    return value

g = example()
# 启动生成器,我们会得到 2
got = g.send(None)
print(got)  # 2

try:
    # 再次启动 会显示 "get 4"
    got = g.send(got*2)
except StopIteration as e:
    # 生成器运行完成,e.value 是返回值
    print(e.value)

To handle nested generators and multiple IO calls, the author builds a stack‑based wrapper that pushes each generator onto a stack, yields IO objects to the event loop, and pops generators when they finish:

def wrapper(gen):
    # 第一层调用 入栈
    stack = Stack()
    stack.push(gen)

    # 开始逐层调用
    while True:
        # 获取栈顶元素
        item = stack.peak()
        result = None
        if isgenerator(item):
            try:
                child = item.send(result)
                stack.push(child)
                continue
            except StopIteration as e:
                result = e.value
        else:  # IO 操作
            result = yield item
        stack.pop()
        if stack.empty():
            print("finished")
            return result

The wrapper is used by the request handler and the event loop maintains a ready list of generators waiting for IO results. When an IO completes, its callback adds the generator back to the ready list.

ready = []

def on_request(request):
    handler = get_handler(request)
    g = wrapper(func1())
    ready.append((g, None))

def process_ready(self):
    def callback(g, result):
        ready.append((g, result))
    for g, result in self.ready:
        io_job = g.send(result)
        asyncio.get_event_loop().io_call(io_job, lambda r: ready.append((g, r)))

To increase extensibility, the article introduces a Future placeholder that represents a result that will be available later. The request function now returns a Future instead of a generator, and the event loop registers a callback on the future:

class Future:
    def set_result(self, result):
        pass
    def result(self):
        pass
    def done(self):
        pass
    def add_done_callback(self, callback):
        pass

def request(url):
    fut = Future()
    def callback(result):
        fut.set_result(result)
    asyncio.get_event_loop().io_call(url, callback)
    return fut

def process_ready(self):
    def callback(fut):
        ready.append((g, fut.result()))
    for g, result in self.ready:
        fut = g.send(result)
        fut.add_done_callback(callback)

The article then traces the historical evolution from early yield -only coroutines, through the introduction of yield from as syntactic sugar for the stack‑based wrapper, to the modern async / await syntax that hides the generator mechanics.

Finally, it compares Python’s coroutine approach with other ecosystems: JavaScript’s Promise , Go’s native goroutine scheduler, and Python libraries like gevent that patch the runtime. The conclusion emphasizes that Python’s native coroutines combine IO multiplexing with generator‑based flow control to provide high‑performance, readable asynchronous code.

PythonAsync/AwaitcoroutineIO MultiplexingGeneratorsAsyncIO
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.