网易首页 > 网易号 > 正文 申请入驻

asyncio 并发任务,如何限制协程的并发数?

0
分享至

剧照:反黑风暴

作者:kingname

来源:未闻Code

有同学问,如果使用asyncio+httpx实现并发请求,怎么限制请求的频率呢?怎么限制最多只能有 x 个请求同时发出呢?我们今天给出两种方案。

提出问题

假设如果我们同时发起12个请求,每个请求的时间不同,那么总共的请求时间大概跟最长耗时的请求差不多。我们先来写一个用于测试的例子:

import asyncio
import httpx
import time

async def req(delay):
print(f'请求一个延迟为{delay}秒的接口')
async with httpx.AsyncClient(timeout=20) as client:
resp = await client.get(f'http://127.0.0.1:8000/sleep/{delay}')
result = resp.json()
print(result)

async def main():
start = time.time()
delay_list = [3, 6, 1, 8, 2, 4, 5, 2, 7, 3, 9, 8]
task_list = []
for delay in delay_list:
task = asyncio.create_task(req(delay))
task_list.append(task)
await asyncio.gather(*task_list)
end = time.time()
print(f'一共耗时:{end - start}')

asyncio.run(main())

这段代码,使用 for 循环创建了12个协程任务,这些任务几乎同时运行,于是,请求完成所有的接口,总共耗时如下图所示:

现在的问题是,由于网站有反爬虫机制,最多只能同时发起3个请求。那么我们怎么确保同一时间最多只有3个协程在请求网络呢?

限制协程任务数

第一个方案跟以前限制多线程的线程数的方案相同。我们创建一个列表,确保列表里面最多只有3个任务,然后持续循环检查,发现有任务完成了,就移除这个完成的任务,并加入一个新的任务,直到待爬的列表为空,这个任务列表也为空。代码如下:

import asyncio
import httpx
import time

async def req(delay):
print(f'请求一个延迟为{delay}秒的接口')
async with httpx.AsyncClient(timeout=20) as client:
resp = await client.get(f'http://127.0.0.1:8000/sleep/{delay}')
result = resp.json()
print(result)

async def main():
start = time.time()
delay_list = [3, 6, 1, 8, 2, 4, 5, 2, 7, 3, 9, 8]
task_list = []
while True:
if not delay_list and not task_list:
break
while len(task_list) < 3:
if delay_list:
delay = delay_list.pop()
task = asyncio.create_task(req(delay))
task_list.append(task)
else:
break
task_list = [task for task in task_list if not task.done()]
await asyncio.sleep(1)
end = time.time()
print(f'一共耗时:{end - start}')

asyncio.run(main())

运行效果如下图所示:

总共耗时大概28秒左右。比串行需要的58秒快了一半,但比全部同时并发多了一倍。

使用 Semaphore

asyncio 实际上自带了一个限制协程数量的类,叫做Semaphore。我们只需要初始化它,传入最大允许的协程数量,然后就可以通过上下文管理器来使用。我们看一下代码:

import asyncio
import httpx
import time

async def req(delay, sem):
print(f'请求一个延迟为{delay}秒的接口')
async with sem:
async with httpx.AsyncClient(timeout=20) as client:
resp = await client.get(f'http://127.0.0.1:8000/sleep/{delay}')
result = resp.json()
print(result)

async def main():
start = time.time()
delay_list = [3, 6, 1, 8, 2, 4, 5, 2, 7, 3, 9, 8]
task_list = []
sem = asyncio.Semaphore(3)
for delay in delay_list:
task = asyncio.create_task(req(delay, sem))
task_list.append(task)
await asyncio.gather(*task_list)

end = time.time()
print(f'一共耗时:{end - start}')

asyncio.run(main())

运行效果如下图所示:

耗时为22秒,比第一个方案更快。

我们来看看Semaphore的用法,它的格式为:

sem = asyncio.Semaphore(同时运行的协程数量)

async def func(sem):
async with sem:
这里是并发执行的代码

task_list = []
for _ in range(总共需要执行的任务数):
task = asyncio.create_task(func(sem))
task_list.append(task)
await asyncio.gather(*task_list)

当我们要限制一个协程的并发数的时候,可以在调用协程之前,先初始化一个Semaphore对象。然后把这个对象传到需要限制并发的协程里面,在协程里面,使用异步上下文管理器包住你的正式代码:

async with sem:
正式代码

这样一来,如果并发数没有达到限制,那么async with sem会瞬间执行完成,进入里面的正式代码中。如果并发数已经达到了限制,那么其他的协程会阻塞在async with sem这个地方,直到正在运行的某个协程完成了,退出了,才会放行一个新的协程去替换掉这个已经完成的协程。

这个写法其实跟多线程的加锁很像。只不过锁是确保同一个时间只有一个线程在运行,而Semaphore可以人为指定能有多少个协程同时运行。

如何限制1分钟内能够运行的协程数

可能同学看了上面的例子以后,只知道如何限制同时运行的协程数。但是怎么限制在一段时间里同时运行的协程数呢?

其实非常简单,在并发的协程里面加个asyncio.sleep就可以了。例如上面的例子,我想限制每分钟只能有3个协程,那么可以把代码改为:

async def req(delay, sem):
print(f'请求一个延迟为{delay}秒的接口')
async with sem:
async with httpx.AsyncClient(timeout=20) as client:
resp = await client.get(f'http://127.0.0.1:8000/sleep/{delay}')
result = resp.json()
print(result)
await asyncio.sleep(60)
总结

如果大家要限制协程的并发数,那么最简单的办法就是使用asyncio.Semaphore。但需要注意的是,只能在启动协程之前初始化它,然后传给协程。要确保所有并发协程拿到的是同一个Semaphore对象。

当然,你的程序里面,可能有多个不同的部分,有些部分限制并发数为 a,有些部分限制并发数为 b。那么你可以初始化多个Semaphore对象,分别传给不同的协程。

还不过瘾?试试它们

如果你觉得本文有帮助

请慷慨分享点赞,感谢啦

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
全国律协:坚决拥护中国共产党领导、拥护我国社会主义法治,努力做党和人民满意的好律师

全国律协:坚决拥护中国共产党领导、拥护我国社会主义法治,努力做党和人民满意的好律师

澎湃新闻
2026-06-27 23:34:05
英格兰当红国脚世界杯期间完成体检,创下豪门转会费纪录

英格兰当红国脚世界杯期间完成体检,创下豪门转会费纪录

足坛超短波
2026-06-26 16:28:02
周末炸雷!37家公司利空齐发 ,立案戴帽风险扎堆

周末炸雷!37家公司利空齐发 ,立案戴帽风险扎堆

慧眼看世界哈哈
2026-06-28 09:59:23
韩国可算确定被淘汰了

韩国可算确定被淘汰了

张佳玮写字的地方
2026-06-28 11:20:44
涡扇15追了十几年,转头一看才发现:全球最强发动机原来是自己?

涡扇15追了十几年,转头一看才发现:全球最强发动机原来是自己?

兵鉴史
2026-06-28 11:32:44
斯卡洛尼:梅西本可以踢满全场,但他选择把上场机会让给队友

斯卡洛尼:梅西本可以踢满全场,但他选择把上场机会让给队友

懂球帝
2026-06-28 13:29:21
我给局长开了3年车,并娶了他的地下情人,新婚夜我才知道赚大了

我给局长开了3年车,并娶了他的地下情人,新婚夜我才知道赚大了

千秋文化
2026-06-26 20:00:22
马斯克力挺库克涨价:内存短缺,百年一遇!美光回怼:涨了45美元,苹果就涨250美元!网友:不好,特斯拉是不是要涨价了?

马斯克力挺库克涨价:内存短缺,百年一遇!美光回怼:涨了45美元,苹果就涨250美元!网友:不好,特斯拉是不是要涨价了?

大白聊IT
2026-06-28 12:22:32
1988年师长李德金开会路上被当场控制,全方位搜身后找到一串钥匙

1988年师长李德金开会路上被当场控制,全方位搜身后找到一串钥匙

磊子讲史
2026-06-23 14:10:47
两日蒸发20亿 东鹏特饮回应创始人“我不喝”视频

两日蒸发20亿 东鹏特饮回应创始人“我不喝”视频

封面新闻
2026-06-28 12:20:02
盘点|亚足联球队整体遇冷,竞争力不升反降

盘点|亚足联球队整体遇冷,竞争力不升反降

北青网-北京青年报
2026-06-28 13:16:26
就在明天!半导体零部件龙头来了

就在明天!半导体零部件龙头来了

21世纪经济报道
2026-06-28 17:29:22
民主刚果3-1战胜乌兹!韩国队正式出局 韩媒解脱:煎熬终于结束了

民主刚果3-1战胜乌兹!韩国队正式出局 韩媒解脱:煎熬终于结束了

风过乡
2026-06-28 09:32:20
金价!大家要有心理准备了,下周,金价或将迎来大风暴

金价!大家要有心理准备了,下周,金价或将迎来大风暴

花小猫的美食日常
2026-06-28 14:40:31
改革、重组,中国央国企马上要迎来一场大洗牌?

改革、重组,中国央国企马上要迎来一场大洗牌?

时尚的弄潮
2026-06-28 12:01:37
彻底凉凉!黄一鸣案判了,结果大快人心,难怪王思聪不认孩子

彻底凉凉!黄一鸣案判了,结果大快人心,难怪王思聪不认孩子

赵昉是个热血青年
2026-06-27 19:49:44
输球急眼了?韩国出局竟怪中国头上,日本网友:以后我们也这么说

输球急眼了?韩国出局竟怪中国头上,日本网友:以后我们也这么说

锐评利物浦
2026-06-28 11:00:30
从CBA到NBA!太阳3年1900万签下练级归来的FMVP古德温

从CBA到NBA!太阳3年1900万签下练级归来的FMVP古德温

晚雾空青
2026-06-28 13:55:54
合同到期!上海全能内线或重返老东家,曾单场砍28+9被李春江看中

合同到期!上海全能内线或重返老东家,曾单场砍28+9被李春江看中

老叶评球
2026-06-28 17:10:23
拉什福德拒绝热刺32.5万周薪,曼联2500万卖不掉!世界杯首发被骂

拉什福德拒绝热刺32.5万周薪,曼联2500万卖不掉!世界杯首发被骂

罗米的曼联博客
2026-06-28 09:29:23
2026-06-28 19:23:00
Python猫 incentive-icons
Python猫
人生苦短,我用Python。博客:https://pythoncat.top
731文章数 8120关注度
往期回顾 全部

科技要闻

DeepSeek最新论文:如何让大模型跑得更快

头条要闻

中央巡视后不久副部级官员任上落马 其上任不到一年半

头条要闻

中央巡视后不久副部级官员任上落马 其上任不到一年半

体育要闻

韩国可算确定被淘汰了

娱乐要闻

曾沛慈拿下《乘风2026》年度总冠军

财经要闻

两只股票撑起的韩国股市,半年熔断 33 次

汽车要闻

搭载华为乾崑六件套 东风奕派M8预售19.98万起

态度原创

手机
旅游
本地
时尚
公开课

手机要闻

与高端、中端有关,荣耀传来三大喜讯

旅游要闻

老君山门票及优惠政策指南

本地新闻

世界杯球迷节:比球赛更好玩的派对

今天的脸不想营业,但墨镜想

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版