import asyncioasync def worker(i): print(f"任务{i}开始") await asyncio.sleep(1) print(f"任务{i}结束") return iasync def main(): tasks = [worker(i) for i in range(10)] results = [] for task in tasks: result = await task results.append(result) print(results)asyncio.run(main())
问题
- 如果有 10000 个任务,同时
gather() 会有什么问题?
标准答案
第一问:不是并发执行
很多人第一眼看到:
tasks = [worker(i) for i in range(10)]
会以为创建了 10 个协程就开始并发了。
实际上这里只是生成了:
[ <coroutine object worker>, <coroutine object worker>, ...]
协程对象而已。
第二问:耗时约 10 秒
因为:
for task in tasks: result = await task
await worker(0)await worker(1)await worker(2)...
任务0开始等待1秒任务0结束任务1开始等待1秒任务1结束...
10个任务 × 1秒
总耗时:≈ 10 秒
第三问:输出顺序
严格按照顺序:
任务0开始任务0结束任务1开始任务1结束任务2开始任务2结束...任务9结束[0,1,2,3,4,5,6,7,8,9]
第四问:正确并发写法
使用:
results = await asyncio.gather( *(worker(i) for i in range(10)))
tasks = [ asyncio.create_task(worker(i)) for i in range(10)]results = await asyncio.gather(*tasks)
任务0开始任务1开始任务2开始...任务9开始(等待1秒)任务0结束任务1结束...任务9结束
第五问:10000 个任务直接 gather 有什么风险?await asyncio.gather( *(worker(i) for i in range(10000)))
问题:
1. 内存暴涨
一次性创建:10000 个 Task
会占用大量内存。
2. 网络打爆
如果是:
那么:10000个TCP连接
可能导致:
3. 事件循环压力巨大
事件循环需要维护:10000个Task状态
调度开销明显增加。
最佳实践:Semaphore 限流
例如最多同时执行 100 个:
import asynciosem = asyncio.Semaphore(100)async def worker(i): async with sem: await asyncio.sleep(1) return iasync def main(): results = await asyncio.gather( *(worker(i) for i in range(10000)) )asyncio.run(main())
面试官追问
asyncio.create_task() 和 await 的本质区别是什么?
优秀回答:
表示:
立即执行,并等待结束。
而:
task = asyncio.create_task(coro())
表示:
把协程注册到事件循环,允许它与其他任务并发运行。
例如:
task1 = asyncio.create_task(fetch_user())task2 = asyncio.create_task(fetch_order())user = await task1order = await task2
总耗时:max(fetch_user, fetch_order)
而不是:fetch_user + fetch_order
一句话总结
创建协程 ≠ 执行协程;await 是排队干活,create_task() 才是真正把活扔进调度中心。 🚀