Python 现代开发学习笔记

(十一)限制同时工作的数量

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。异步队列

两个工作任务和两格队列共同推进,队列满时生产者等待空位

笔记正文与全部题解 · Ctrl / ⌘ K