(十一)限制同时工作的数量
1. 用信号量管理进入操作的名额
# 项目文件:demos/semaphore_limit.py
# 三个任务只允许两个同时进入等待操作;退出 async with 自动归还名额。
import asyncio
async def main():
semaphore = asyncio.Semaphore(2)
active = peak = 0
async def work(value):
nonlocal active, peak
async with semaphore:
active += 1
peak = max(peak, active)
try:
await asyncio.sleep(0)
return value * 2
finally:
active -= 1
async with asyncio.TaskGroup() as group:
tasks = [group.create_task(work(value)) for value in [20, 35, 45]]
print([task.result() for task in tasks], "最多同时进入", peak)
if __name__ == "__main__":
asyncio.run(main())输出 [40, 70, 90] 最多同时进入 2。前两个任务取得名额,第三个等待;一个退出后,第三个才能进入。这里 active 与 peak 只在同一事件循环线程中更新,读取和写回之间没有 await。异步同步工具
2. 用队列限制等待中的数据
信号量控制进入操作的数量,但先建立十万个 Task,仍然要保存十万个任务。可以只建立固定数量的工作任务,再由队列交给它们下一项数据:
# 项目文件:demos/async_queue.py
# 两个工作任务处理五项输入,队列至多保存两项尚未取出的数据。
import asyncio
async def consume(queue, results):
while True:
item = await queue.get()
try:
if item is None:
return
await asyncio.sleep(0)
results.append((item, item * item))
finally:
queue.task_done()
async def main():
queue, results = asyncio.Queue(maxsize=2), []
async with asyncio.TaskGroup() as group:
workers = [group.create_task(consume(queue, results)) for _ in range(2)]
for value in range(5):
await queue.put(value)
for _ in workers:
await queue.put(None)
await queue.join()
print([result for value, result in sorted(results)])
if __name__ == "__main__":
asyncio.run(main())输出 [0, 1, 4, 9, 16]。put 在队列满时等待;get 在空时等待。None 是本例约定的结束标记,0 仍是要计算的数据。每次取得数据后用 task_done 抵消一项未完成计数,join 等到这些计数都完成,任务组再等工作任务退出。
队列的 maxsize 限制尚未取出的数据,不包含工作任务已经取走、正在处理的项。asyncio.Queue 服务于同一事件循环,前面的线程间通信使用 queue.Queue。异步队列