协程

现代Python协程编程指南

协程基础概念

协程(Coroutine),又称微线程或纤程,是一种比线程更轻量级的并发执行单元。与传统的子程序(函数)调用不同,协程允许在执行过程中暂停并在之后恢复。

协程 vs 子程序

特性子程序(函数)协程
执行流程严格的层级调用,一次入口一次返回可中断和恢复,多个入口点
调用方式直接调用通过yield/send协作
执行上下文每次调用独立栈帧保持执行状态
并发模型同步阻塞异步非阻塞

协程的优势

  1. 极高的执行效率:协程切换由程序控制,没有线程切换的开销
  2. 无需锁机制:单线程执行避免了多线程的竞争条件
  3. 高并发能力:单个线程可支持大量协程并发
  4. 简化异步编程:以同步代码风格实现异步逻辑

Python中的协程演进

1. 基于生成器的协程(Python 2.5+)

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print(f'[CONSUMER] Consuming {n}...')
        r = '200 OK'

def producer(c):
    c.send(None)  # 启动生成器
    for n in range(1, 6):
        print(f'[PRODUCER] Producing {n}...')
        r = c.send(n)
        print(f'[PRODUCER] Consumer returned: {r}')
    c.close()

2. 使用asyncio的协程(Python 3.4+)

import asyncio

async def consumer(n):
    print(f'[CONSUMER] Consuming {n}...')
    await asyncio.sleep(1)
    return '200 OK'

async def producer():
    for n in range(1, 6):
        print(f'[PRODUCER] Producing {n}...')
        r = await consumer(n)
        print(f'[PRODUCER] Consumer returned: {r}')

asyncio.run(producer())

3. 现代Python协程(Python 3.7+)

Python 3.7引入了asyncio.run()等简化API:

import asyncio

async def task(name, delay):
    print(f"{name} started")
    await asyncio.sleep(delay)
    print(f"{name} completed after {delay}s")
    return delay

async def main():
    # 并发执行多个协程
    results = await asyncio.gather(
        task("Task1", 2),
        task("Task2", 1),
        task("Task3", 3)
    )
    print(f"All tasks completed with results: {results}")

asyncio.run(main())

协程核心概念

1. 事件循环(Event Loop)

协程的执行依赖于事件循环,它负责调度和执行协程任务。

import asyncio

async def hello_world():
    print("Hello")
    await asyncio.sleep(1)
    print("World")

loop = asyncio.get_event_loop()
loop.run_until_complete(hello_world())
loop.close()

2. 可等待对象(Awaitables)

Python中有三种主要可等待对象:

  • 协程(Coroutines)
  • 任务(Tasks)
  • 未来对象(Futures)

3. 协程与任务

import asyncio

async def nested():
    return 42

async def main():
    # 直接await协程
    print(await nested())  
    
    # 创建任务
    task = asyncio.create_task(nested())
    print(await task)  

asyncio.run(main())

高级协程模式

1. 协程并发执行

import asyncio

async def fetch_data(delay, id):
    print(f"Fetching data {id}...")
    await asyncio.sleep(delay)
    print(f"Data {id} fetched")
    return {"id": id, "delay": delay}

async def main():
    tasks = [
        asyncio.create_task(fetch_data(2, 1)),
        asyncio.create_task(fetch_data(1, 2)),
        asyncio.create_task(fetch_data(3, 3))
    ]
    
    # 等待第一个完成
    done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
    print(f"First task completed: {done.pop().result()}")
    
    # 等待剩余任务完成
    await asyncio.wait(pending)

asyncio.run(main())

2. 协程与线程池结合

import asyncio
import time
from concurrent.futures import ThreadPoolExecutor

def blocking_io():
    print(f"Start blocking IO at {time.strftime('%X')}")
    time.sleep(2)  # 模拟阻塞IO操作
    print(f"Blocking IO done at {time.strftime('%X')}")
    return "IO result"

async def main():
    print(f"Started main at {time.strftime('%X')}")
    
    # 在默认线程池中运行阻塞IO
    result = await asyncio.get_event_loop().run_in_executor(
        None, blocking_io)
    print(f"Result: {result}")
    
    print(f"Finished main at {time.strftime('%X')}")

asyncio.run(main())

3. 协程超时控制

import asyncio

async def long_running_task():
    try:
        print("Task started")
        await asyncio.sleep(3600)  # 模拟长时间运行的任务
        return "Task completed"
    except asyncio.CancelledError:
        print("Task cancelled")
        raise

async def main():
    try:
        async with asyncio.timeout(1.0):
            await long_running_task()
    except TimeoutError:
        print("Timeout occurred")

asyncio.run(main())

最佳实践

  1. 避免阻塞操作:在协程中不要使用同步阻塞调用
  2. 合理使用并发asyncio.gather()适合并行执行独立任务
  3. 资源管理:使用async with管理异步资源
  4. 错误处理:妥善处理协程中的异常
  5. 性能监控:使用asyncio调试模式检测未等待的协程

总结

现代Python协程通过async/await语法提供了清晰简洁的异步编程模型。相比传统的生成器协程,它具有以下优势:

  1. 更直观的语法
  2. 更好的错误处理
  3. 与异步IO库的深度集成
  4. 更强大的并发控制能力

正如计算机科学家Donald Knuth所言:"子程序就是协程的一种特例。"掌握协程将帮助你编写出更高效、更易维护的并发程序。