跳至内容

协程

**协程(Coroutine)**是一种用户态的轻量级并发单元,由程序自身控制调度(而非操作系统)。Python 3.5+ 通过 async/await 语法原生支持协程,标准库 asyncio 提供完整的事件循环和工具集。

本篇是协程基础概念,更完整的 asyncio 用法(gatherTaskGrouptimeout、异步上下文管理器等)见 异步编程与 asyncio

协程 vs 线程 vs 进程

维度协程线程进程
调度者用户程序操作系统操作系统
内存开销极小(几 KB)较大(MB 级)最大(独立地址空间)
切换成本极低(函数调用级)较高(内核态切换)最高
并行能力无(单线程)受 GIL 限制真正并行(多核)
适合场景I/O 密集,海量连接I/O 密集,遗留同步代码CPU 密集

async/await 基础

import asyncio

async def greet(name: str, delay: float) -> str:
    """async def 定义协程函数,调用后返回协程对象(不会立即执行)。"""
    print(f"{name} 开始等待 {delay}s")
    await asyncio.sleep(delay)   # await 挂起当前协程,让出事件循环
    return f"Hello, {name}!"

async def main() -> None:
    # 串行执行:总耗时 1+2 = 3s
    r1 = await greet("Alice", 1.0)
    r2 = await greet("Bob", 2.0)
    print(r1, r2)

asyncio.run(main())   # Python 3.7+,创建事件循环并运行

并发执行

await 本身是串行的;要实现并发,需要用 gathercreate_task

import asyncio
import time

async def fetch(name: str, delay: float) -> str:
    await asyncio.sleep(delay)
    return f"{name} done"

async def main() -> None:
    start = time.perf_counter()

    # gather:并发运行,总耗时约 max(1, 2, 1.5) ≈ 2s
    results = await asyncio.gather(
        fetch("任务A", 1.0),
        fetch("任务B", 2.0),
        fetch("任务C", 1.5),
    )

    print(results)
    print(f"总耗时: {time.perf_counter() - start:.2f}s")

asyncio.run(main())

任务与取消

import asyncio

async def long_task(n: int) -> int:
    await asyncio.sleep(n)
    return n

async def main() -> None:
    # create_task 立即提交到事件循环
    task = asyncio.create_task(long_task(5))

    await asyncio.sleep(2)
    task.cancel()   # 取消任务

    try:
        await task
    except asyncio.CancelledError:
        print("任务已取消")

asyncio.run(main())

协程的优缺点

优点

  • 切换开销极小(用户态切换,不涉及内核)。
  • 单线程内实现高并发,不需要锁(共享状态在同一线程内,await 点是唯一切换时机)。
  • 可以支撑数万乃至数十万个并发连接(如 Web 服务器)。

缺点

  • 本质是单线程,无法利用多核 CPU(CPU 密集型任务用 ProcessPoolExecutor)。
  • 一旦某个协程执行了阻塞的同步 I/O(如 time.sleep),会阻塞整个事件循环。
  • 必须全链路使用 async/await,混入同步阻塞调用需要用 loop.run_in_executor()

在协程中调用阻塞函数

import asyncio
from concurrent.futures import ThreadPoolExecutor

def blocking_read(path: str) -> str:
    """同步阻塞的文件读取(遗留代码)。"""
    with open(path, encoding="utf-8") as f:
        return f.read()

async def main() -> None:
    loop = asyncio.get_event_loop()
    # run_in_executor 把阻塞调用放到线程池中,不阻塞事件循环
    with ThreadPoolExecutor() as pool:
        content = await loop.run_in_executor(pool, blocking_read, "README.md")
    print(content[:100])

asyncio.run(main())

历史背景(了解)

Python 协程经历了多个演变阶段:

阶段技术特点
Python 2generatoryield手动切换,无标准调度
Python 2/3greenlet(第三方)显式 switch(),需手动调度
Python 2/3gevent(第三方)基于 greenlet + monkey patch 自动切换
Python 3.4asyncio(标准库)基于 yield from,事件循环标准化
Python 3.5+async/await(当前标准)语法清晰,生态完整

当前项目应直接使用 async/await + asynciogreenletgevent 仅出现在遗留代码中。

最后更新于