三 Python协程
3.1 题目
写一个async def produce()往list追加 + async.Event.set(),一个async def consume()循环await event.wait() -> clear -> 取空列表,跑起来观察行为;然后故意把顺序改成"先取再clear",看是否丢消息
3.2 参考答案
python
import asyncio
# """
# asyncio.Event: 生产者消费者与丢消息
# 实验目的: 理解asyncio.Event的通知机制,以及为什么Event不能直接当作消息队列使用
# """
async def main():
queue = []
event = asyncio.Event()
async def producer():
for i in range(5):
print(f"Producer: Producing item {i}")
queue.append(i)
event.set() # 通知消费者有新消息
await asyncio.sleep(0.1) # 模拟生产时间
async def consumer():
result = []
while len(result) < 5:
await event.wait() # 等待生产者通知
event.clear() # 清除事件状态,以便下次等待
batch = queue[:]
queue.clear()
for item in batch:
print(f"Consumer: Consuming item: {item}")
result.append(item)
await asyncio.sleep(3) # 模拟消费时间
return result
await asyncio.gather(producer(), consumer())
asyncio.run(main())
运行结果:

3.3 执行顺序
关键操作:
python
await event.wait()
event.clear()
batch = queue[:]
queue.clear()
| 操作 | 作用 |
|---|---|
| event.set() | 设置事件,唤醒等待者 |
| event.wait() | 事件未设置时挂起,设置后继续 |
| event.clear() | 重置事件,恢复未触发状态 |
这里有个重要概念:Event是状态通知,不是消息队列
连续调用多次set(),并不会累计通知次数
实际工程中:
- 需要传递消息:优先使用asyncio.Queue
- 只需要通知状态变化:使用asyncio.Event
- 需要保证通知和数据状态一致:设计同步协议,不能简单依赖Event
3.3.1 先取在clear
python
import asyncio
# """
# asyncio.Event: 生产者消费者与丢消息
# 实验目的: 理解asyncio.Event的通知机制,以及为什么Event不能直接当作消息队列使用
# """
async def main():
queue = []
event = asyncio.Event()
async def producer():
for i in range(3):
print(f"Producer: Producing item {i}")
queue.append(i)
event.set() # 通知消费者有新消息
await asyncio.sleep(0.1) # 模拟生产时间
async def consumer():
result = []
while len(result) < 3:
await event.wait() # 等待生产者通知
event.clear() # 清除事件状态,以便下次等待
batch = queue[:]
queue.clear()
await asyncio.sleep(0)
event.clear()
for item in batch:
print(f"Consumer: Consuming item: {item}")
result.append(item)
await asyncio.sleep(0.1) # 模拟消费时间
return result
await asyncio.gather(producer(), consumer())
asyncio.run(main())
执行结果:

结论:
可能会暴露丢失唤醒的问题,但在以上代码不会暴露
注意:CPython的asyncio实现对sleep(0)做了特殊优化,使用一个专门的协程路径,让当前Task暂停,并在后续事件循环迭代中恢复
如何稳定复现丢失唤醒
python
import asyncio
async def main():
queue = []
event = asyncio.Event()
async def producer():
queue.append(0)
event.set()
print("Producer: 生产 0")
await asyncio.sleep(0)
queue.append(1)
event.set()
print("Producer: 生产 1")
async def consumer():
await event.wait()
event.clear()
batch = queue[:]
queue.clear()
print("Consumer: 取走", batch)
# 让生产者有机会追加第二条消息并 set
await asyncio.sleep(0)
# 故意清除生产者刚设置的通知
event.clear()
print("Consumer: 清除事件,当前队列:", queue)
# 再次等待
await event.wait()
print("Consumer: 被唤醒")
await asyncio.gather(producer(), consumer())
asyncio.run(main())
你会发现程序一直无法正常结束。
原因是:
-
消费者取走第一条消息。
-
消费者执行
sleep(0)。 -
生产者追加第二条消息,并调用
event.set()。 -
消费者恢复,调用
event.clear()。 -
消费者再次等待。
-
队列中有第二条消息,但 Event 已经被清除。
3.4 核心结论
既然没有await就不会发生协程切换,那为什么还要使用asyncio.Queue?
因为:asyncio.Event只表示一个布尔状态
python
event.set() # True
event.clear() # False
它不记录消息数量,也不负责维护消息与通知之间的一致性
而asyncio.Queue把数据存储和等待通知封装存储到一起
python
await queue.put(item)
item = await queue.get()
这就不需要自己维护list+Event的同步关系
要点:在单线程asyncio中,协程切换发生在挂起点,而不是任意Python语句之间。这一点与Go Goroutine的调度模型有明显区别。
四
4.1 题目
写 def gen()和async def gen()各产3个数,分别用for和async for消费,确认两者不能混用
4.2 参考答案
python
import asyncio
"""
实验目的:理解同步生成器和异步生成器的区别,以及for和async for的使用边界
"""
def gen():
for i in range(5):
yield i
async def agen():
for i in range(5):
await asyncio.sleep(0.1)
yield i
for i in gen():
print(f"Sync generator: {i}")
async def main():
async for i in agen():
print(f"Async generator: {i}")
asyncio.run(main())
运行结果:

gen的结果和async的结果不能混用
python
for item in agen():
pass
会报错:
python
TypeError: 'async_generator' object is not iterable
反之:
python
async for item in gen():
pass
也会报错,因为同步生成器不支持异步迭代协议。
记住两个协议:
-
同步迭代:
__iter__+__next__ -
异步迭代:
__aiter__+__anext__
4.3 知识点
| 对比 | Generator | Async Generator |
|---|---|---|
| 定义 | def + yield |
async def + yield |
| 消费 | for |
async for |
| 获取下一个元素 | next() |
__anext__() |
| 是否支持 await | 不支持 | 支持 |
| 典型场景 | 同步迭代 | 异步 IO 流 |
4.4 异步IO流
可以理解为:数据不是一次性全部返回,而是随着IO操作的进行,分批、持续的异步产生或接收,程序可以在等待下一批数据时执行其他任务
关键词:
- IO:输入输出,例如网络请求、文件读取、Socket通信
- 流(Stream):数据连续产生或传输,不一定一次性获取完整
- 异步(Async):等待数据期间,当前协程可以挂起,让事件循环调度其他协程
4.5 实现异步IO流
python
import asyncio
async def data_stream():
for i in range(3):
await asyncio.sleep(1)
yield f"data-{i}"
async def main():
async for item in data_stream():
print(item)
asyncio.run(main())
实际开发中的应用场景:
- HTTP Streaming,例如大文件下载、流式API
- LLM流式输出,例如ChatGPT的逐字输出,常使用SSE
- WebSocket,例如在线聊天、实时状态推送