一、协程基础概念
1.1 协程定义
协程(Coroutine)是用户空间的轻量级多任务机制,允许在单个线程内通过上下文切换实现协作式多任务处理,可暂停并恢复执行,模拟并发效果。
- 核心区别:进程/线程由操作系统调度,协程由程序员控制,不依赖OS直接支持。
1.2 协程核心特点
| 特点 | 说明 |
|---|---|
| 资源占用少 | 仅需少量栈空间,通过状态保存/恢复实现切换,远低于进程/线程 |
| 切换开销小 | 完全在用户态完成,无需内核态上下文切换,开销极低 |
| 可暂停/恢复 | 遇I/O等耗时操作时主动暂停,释放CPU,适合I/O密集型任务 |
1.3 与进程/线程的对比
| 维度 | 进程 | 线程 | 协程 |
|---|---|---|---|
| 调度者 | 操作系统 | 操作系统 | 程序员(用户态) |
| 资源占用 | 高(独立内存) | 中(共享进程内存) | 低(仅少量栈空间) |
| 切换开销 | 极高(内核态) | 中(内核态) | 极低(用户态) |
| 适用场景 | CPU密集型、独立进程隔离 | 多任务并发(需处理线程安全) | I/O密集型(如网络请求、文件读写) |
二、Python协程实现方法
2.1 主流实现:async/await(Python 3.5+)
核心规则:
async def定义协程函数,调用时返回协程对象,不直接执行代码;await仅能在协程函数内使用,用于挂起协程,等待可等待对象(协程/Task/Future)完成。
代码示例:
python
import asyncio
import time
# 定义协程函数
async def say_after(delay, msg):
await asyncio.sleep(delay) # 挂起协程,等待延迟结束(非阻塞)
print(msg)
# 主协程(程序入口)
async def main():
print(f"开始时间: {time.strftime('%X')}")
# 并发执行两个协程(需封装为Task,或用gather自动封装)
await asyncio.gather(
say_after(1, "hello"),
say_after(2, "world")
)
print(f"结束时间: {time.strftime('%X')}") # 总耗时≈2秒(并发效果)
# 启动协程(Python 3.7+推荐用法)
asyncio.run(main())
2.2 其他实现方式(了解即可)
yield关键字(早期方式):
python
def consumer():
print("消费者准备接收数据")
while True:
data = (yield) # 暂停并接收数据
print(f"消费者接收: {data}")
def producer(consumer_gen):
next(consumer_gen) # 启动生成器
for i in range(3):
print(f"生产者发送: {i}")
consumer_gen.send(i) # 发送数据给消费者
consumer_gen.close() # 终止
# 运行
consumer_coro = consumer()
producer(consumer_coro)
asyncio.coroutine装饰器(Python 3.4,3.8弃用):
python
import asyncio
@asyncio.coroutine
def func():
print("123")
yield from asyncio.sleep(2) # 类似await的作用
print("456")
# 启动(Python 3.7前)
loop = asyncio.get_event_loop()
loop.run_until_complete(func())
loop.close()
三、协程核心组件
3.1 事件循环(Event Loop)
事件循环是协程的"调度中心",负责管理协程的执行、暂停与恢复,流程如下:
- 启动循环 → 2. 注册事件(如定时器、I/O) → 3. 循环等待事件 → 4. 执行/恢复协程 → 5. 任务完成后关闭。
版本差异:
- Python 3.7前:需手动获取循环
python
import asyncio
async def func():
await asyncio.sleep(1)
print("执行完成")
# 3.7前用法
loop = asyncio.get_event_loop()
loop.run_until_complete(func())
loop.close()
- Python 3.7+:
asyncio.run()自动创建/关闭循环(推荐)
python
asyncio.run(func()) # 一行代码启动
3.2 Task对象
Task是协程的"封装器",将协程对象提交到事件循环并调度执行,支持查看任务状态、获取结果。
创建方式:
| Python版本 | 方法 | 示例 |
|---|---|---|
| 3.7前 | asyncio.ensure_future() |
task = asyncio.ensure_future(func()) |
| 3.7+ | asyncio.create_task()(推荐) |
task = asyncio.create_task(func()) |
代码示例:
python
import asyncio
async def func(name, delay):
await asyncio.sleep(delay)
return f"{name} 完成(延迟{delay}秒)"
async def main():
# 创建Task
task1 = asyncio.create_task(func("任务1", 2))
task2 = asyncio.create_task(func("任务2", 1))
# 等待Task完成并获取结果
result1 = await task1
result2 = await task2
print(result1, result2)
asyncio.run(main())
四、协程通信机制(asyncio.Queue)
协程间通过消息队列通信,asyncio.Queue 提供三种队列类型,均支持异步操作(await put()/await get())。
4.1 三种队列对比
| 队列类型 | 特点 | 适用场景 |
|---|---|---|
Queue |
先进先出(FIFO) | 普通顺序通信(如生产者-消费者) |
LifoQueue |
后进先出(LIFO) | 栈式场景(如任务优先级反转) |
PriorityQueue |
按优先级排序(元组第一个元素为优先级) | 高优先级任务优先处理 |
4.2 代码示例(生产者-消费者模型)
python
import asyncio
# 生产者协程:向队列放数据
async def producer(queue):
for i in range(1, 4):
print(f"生产者放入: {i}")
await queue.put(i) # 队列满时阻塞
await asyncio.sleep(1) # 模拟生产耗时
await queue.put(None) # 发送结束信号
# 消费者协程:从队列取数据
async def consumer(queue):
while True:
item = await queue.get() # 队列空时阻塞
if item is None: # 接收结束信号
queue.task_done() # 标记任务完成
break
print(f"消费者取出: {item}")
queue.task_done() # 标记任务完成
async def main():
queue = asyncio.Queue(maxsize=2) # 最大容量2
await asyncio.gather(producer(queue), consumer(queue))
await queue.join() # 等待所有任务处理完毕
print("所有任务完成")
asyncio.run(main())
五、协程同步机制
协程共享线程资源,需同步机制避免数据竞争,asyncio 提供四种核心同步工具:
5.1 Lock(互斥锁)
确保同一时间仅一个协程访问共享资源,类似线程的threading.Lock。
代码示例:
python
import asyncio
async def worker(lock, id):
while True:
async with lock: # 自动获取/释放锁,避免死锁
print(f"Worker {id} 正在执行")
await asyncio.sleep(1) # 模拟耗时操作
async def main():
lock = asyncio.Lock()
# 并发运行2个worker,同一时间仅1个执行
await asyncio.gather(worker(lock, 1), worker(lock, 2))
asyncio.run(main())
5.2 Semaphore(信号量)
控制同时访问资源的协程数量,通过内部计数器实现(计数器>0可获取,=0阻塞)。
代码示例(停车场场景):
python
import asyncio
async def car(semaphore, car_id):
print(f"车辆{car_id} 等待停车位")
async with semaphore: # 获取信号量(占用1个车位)
print(f"车辆{car_id} 进入停车场")
await asyncio.sleep(2) # 停留2秒
print(f"车辆{car_id} 离开停车场")
async def main():
semaphore = asyncio.Semaphore(3) # 仅3个停车位
cars = [car(semaphore, i) for i in range(1, 6)] # 5辆车
await asyncio.gather(*cars)
asyncio.run(main())
5.3 Event(事件)
用于协程间"通知",一个协程设置事件,多个协程等待事件触发。
代码示例:
python
import asyncio
async def producer(event):
print("生产者准备数据...")
await asyncio.sleep(2) # 模拟准备耗时
event.set() # 触发事件(通知消费者)
print("生产者已通知")
async def consumer(event):
print("消费者等待通知...")
await event.wait() # 阻塞,直到事件被设置
print("消费者收到通知,开始处理")
async def main():
event = asyncio.Event()
await asyncio.gather(producer(event), consumer(event))
asyncio.run(main())
5.4 Condition(条件变量)
结合Lock和Event功能,支持协程等待特定条件成立后再执行(如"队列非空")。
代码示例:
python
import asyncio
async def producer(cond):
while True:
async with cond: # 获取锁
cond.notify_all() # 通知所有等待的消费者
await asyncio.sleep(1)
async def consumer(cond, id):
while True:
async with cond:
print(f"消费者{id} 等待通知")
await cond.wait() # 释放锁并等待通知
print(f"消费者{id} 收到通知")
async def main():
cond = asyncio.Condition()
tasks = [
asyncio.create_task(producer(cond)),
*[asyncio.create_task(consumer(cond, i)) for i in range(3)]
]
await asyncio.wait(tasks)
asyncio.run(main())
六、协程与线程池/进程池结合
协程适合I/O密集型任务,CPU密集型任务需结合进程池 (避免GIL锁限制)。asyncio 提供工具将线程池/进程池任务转为可等待对象。
6.1 核心方法
asyncio.wrap_future():将concurrent.futures.Future转为asyncio.Future;loop.run_in_executor():直接在执行器中运行同步函数,返回asyncio.Future(推荐)。
6.2 代码示例(协程+进程池)
python
import asyncio
import concurrent.futures
import time
# CPU密集型同步函数(耗时计算)
def cpu_intensive_task(n):
result = sum(i*i for i in range(n))
print(f"计算完成: sum(0~{n-1})² = {result}")
return result
async def main():
# 获取当前事件循环
loop = asyncio.get_running_loop()
# 使用进程池执行CPU密集型任务
with concurrent.futures.ProcessPoolExecutor() as pool:
# 提交任务到进程池,返回asyncio.Future
task1 = loop.run_in_executor(pool, cpu_intensive_task, 10**6)
task2 = loop.run_in_executor(pool, cpu_intensive_task, 10**6)
# 等待结果
result1 = await task1
result2 = await task2
print(f"最终结果: {result1}, {result2}")
if __name__ == "__main__": # 进程池必须在主模块中运行
start = time.time()
asyncio.run(main())
print(f"总耗时: {time.time() - start:.2f}秒")
七、关键总结
- 适用场景:协程优先用于I/O密集型任务(如网络请求、文件读写),CPU密集型需结合进程池;
- 核心关键字 :
async def(定义协程)、await(挂起协程); - 版本推荐 :Python 3.7+使用
asyncio.run()、asyncio.create_task()简化代码; - 同步与通信 :通过
asyncio.Queue通信,Lock/Semaphore/Event/Condition实现同步。