协程详解
什么是协程?
协程(Coroutine)是比线程更轻量级的并发单位,它是一种可以在执行中暂停并切换到其他任务的函数。协程本质上是单线程的,但通过协作式调度实现了高效的并发处理。
协程的特点:
- 轻量级: 协程不像线程那样依赖于操作系统,而是在程序内部实现的,因此开销非常小。
- 非阻塞: 协程通过主动让出控制权来实现并发(协作式调度),而不是依赖操作系统的线程切换(抢占式调度)。
- 适合 I/O 密集型任务: 协程特别适合需要频繁等待 I/O 操作的场景,如网络请求、文件读取等。
协程与线程的区别:
特性 | 线程 | 协程 |
---|---|---|
切换方式 | 由操作系统调度,抢占式 | 程序员控制,协作式 |
性能开销 | 切换需要操作系统上下文切换,较高 | 切换仅需保存函数状态,开销非常低 |
并行能力 | 可利用多核(多线程) | 单线程,不支持多核 |
适用场景 | 适合 CPU 密集型任务 | 适合 I/O 密集型任务 |
Python 中的协程实现
Python 中支持协程的主要工具是 asyncio
模块,它基于 async/await
关键字。
基础用法:
- 定义协程:
使用
async def
定义一个协程函数。
async def my_coroutine():
print("Start coroutine")
await asyncio.sleep(1) # 模拟 I/O 操作
print("End coroutine")
- 运行协程: 协程函数返回一个协程对象,需要用 事件循环(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
关键点详解:
await
的作用:await
用于暂停当前协程,等待一个异步操作完成。-
例如,
await asyncio.sleep(1)
会让当前协程暂停 1 秒,同时将控制权让给事件循环。 -
asyncio.run()
: - 是启动协程的高层接口,用于运行一个协程直到完成。
-
它会自动管理事件循环的创建和关闭。
-
asyncio.gather()
: - 接收多个协程对象,调度它们并发执行。
-
返回一个包含所有任务结果的列表。
-
asyncio.create_task()
: - 显式创建一个任务,并允许任务在后台运行。
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())
协程的应用场景
- 网络请求: 使用协程并发处理多个网络请求,如抓取网页。
- 文件 I/O: 异步读写文件。
- 数据库操作: 使用异步数据库库(如
aiomysql
)。 - 实时通信: 异步 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
提供的丰富功能,可以轻松实现复杂的异步逻辑。