跳转至

协程详解

什么是协程?

协程(Coroutine)是比线程更轻量级的并发单位,它是一种可以在执行中暂停并切换到其他任务的函数。协程本质上是单线程的,但通过协作式调度实现了高效的并发处理。


协程的特点:

  1. 轻量级: 协程不像线程那样依赖于操作系统,而是在程序内部实现的,因此开销非常小。
  2. 非阻塞: 协程通过主动让出控制权来实现并发(协作式调度),而不是依赖操作系统的线程切换(抢占式调度)。
  3. 适合 I/O 密集型任务: 协程特别适合需要频繁等待 I/O 操作的场景,如网络请求、文件读取等。

协程与线程的区别:

特性 线程 协程
切换方式 由操作系统调度,抢占式 程序员控制,协作式
性能开销 切换需要操作系统上下文切换,较高 切换仅需保存函数状态,开销非常低
并行能力 可利用多核(多线程) 单线程,不支持多核
适用场景 适合 CPU 密集型任务 适合 I/O 密集型任务

Python 中的协程实现

Python 中支持协程的主要工具是 asyncio 模块,它基于 async/await 关键字。

基础用法:

  1. 定义协程: 使用 async def 定义一个协程函数。
async def my_coroutine():
    print("Start coroutine")
    await asyncio.sleep(1)  # 模拟 I/O 操作
    print("End coroutine")
  1. 运行协程: 协程函数返回一个协程对象,需要用 事件循环(Event Loop) 来运行。
import asyncio

async def my_coroutine():
    print("Start coroutine")
    await asyncio.sleep(1)  # 模拟 I/O 操作
    print("End coroutine")

asyncio.run(my_coroutine())  # 运行协程

输出:

Start coroutine
End coroutine


并发运行多个协程:

通过 asyncio.gather()asyncio.create_task() 实现并发运行多个协程。

import asyncio

async def task(name, duration):
    print(f"Task {name} started")
    await asyncio.sleep(duration)  # 模拟 I/O
    print(f"Task {name} finished")

async def main():
    # 并发运行多个任务
    await asyncio.gather(
        task("A", 2),
        task("B", 1),
        task("C", 3)
    )

asyncio.run(main())

输出(顺序可能不同,具体取决于调度):

Task A started
Task B started
Task C started
Task B finished
Task A finished
Task C finished


关键点详解:

  1. await 的作用:
  2. await 用于暂停当前协程,等待一个异步操作完成。
  3. 例如,await asyncio.sleep(1) 会让当前协程暂停 1 秒,同时将控制权让给事件循环。

  4. asyncio.run()

  5. 是启动协程的高层接口,用于运行一个协程直到完成。
  6. 它会自动管理事件循环的创建和关闭。

  7. asyncio.gather()

  8. 接收多个协程对象,调度它们并发执行。
  9. 返回一个包含所有任务结果的列表。

  10. asyncio.create_task()

  11. 显式创建一个任务,并允许任务在后台运行。
async def main():
    task = asyncio.create_task(task("Background", 2))
    print("Doing something else...")
    await asyncio.sleep(1)
    print("Waiting for task to finish...")
    await task

asyncio.run(main())

协程的应用场景

  1. 网络请求: 使用协程并发处理多个网络请求,如抓取网页。
  2. 文件 I/O: 异步读写文件。
  3. 数据库操作: 使用异步数据库库(如 aiomysql)。
  4. 实时通信: 异步 WebSocket 或聊天服务。

综合示例:

模拟爬取多个网页内容:

import asyncio
import random

async def fetch_page(url):
    print(f"Fetching {url}...")
    await asyncio.sleep(random.uniform(1, 3))  # 模拟网络延迟
    print(f"Finished {url}")
    return f"Content of {url}"

async def main():
    urls = ["http://example.com/1", "http://example.com/2", "http://example.com/3"]
    results = await asyncio.gather(*(fetch_page(url) for url in urls))
    print("Results:", results)

asyncio.run(main())

输出:

Fetching http://example.com/1...
Fetching http://example.com/2...
Fetching http://example.com/3...
Finished http://example.com/2
Finished http://example.com/1
Finished http://example.com/3
Results: ['Content of http://example.com/1', 'Content of http://example.com/2', 'Content of http://example.com/3']


总结

协程是 Python 中实现高效并发的重要工具,它通过 async/await 语法实现了易用性和性能的统一,非常适合处理 I/O 密集型任务。通过 asyncio 提供的丰富功能,可以轻松实现复杂的异步逻辑。