You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在循环中正确运行asyncio/aiohttp异步请求?

Hey there! 你已经用协程把请求速度提了10倍,这已经很棒了,但锁占比过高确实是影响进一步优化的关键问题。咱们一步步来拆解你的疑问,先验证实现正确性,再解决锁的问题:

协程并发请求:正确性验证与锁优化指南

一、先确认你的协程实现是否真的“正确异步”

你提到通过遍历列表加队列实现并发,这里有几个常见的坑,你可以对照自查:

  • 有没有混用同步HTTP库? 如果你的协程里还是调用requests.get()(纯同步阻塞),那所谓的“并发”其实是伪异步——速度快10倍可能只是队列批量调度减少了一些串行等待,但本质还是在逐个等请求完成,而且队列的同步锁会让你花大量时间在锁状态。正确姿势是用aiohttp或者httpx.AsyncClient这类异步HTTP客户端。
  • 队列用对了吗? 如果用的是queue.Queue(线程安全但对协程不友好),而不是asyncio.Queue,那每次put()/get()都会触发线程锁竞争,这大概率是你90%时间卡在锁的元凶!asyncio.Queue是专为协程设计的,用协程级别的等待替代了线程锁,完全没有锁竞争开销。
  • 有没有控制并发数? 10+并发如果没做限流(比如用asyncio.Semaphore),瞬间发起大量请求可能触发目标服务器的限流策略,导致后续请求被挂起,看起来像是锁等待。

二、锁占比过高的核心原因分析

你说“并发请求完成后仍有90%的时间处于锁状态”,结合你的流程(启动→并发→锁定5秒→完成),大概率是结果收集或队列管理环节的同步阻塞:

  • 同步队列的锁竞争:queue.Queue的操作都是线程阻塞的,在协程环境里会把整个事件循环卡住,导致大量时间浪费在抢锁上;
  • 结果收集用了同步容器:比如用普通list加threading.Lock来存结果,每次写入都要抢锁,自然占比极高。

三、优化方案(附实战代码)

1. 替换为异步友好的组件,抛弃手动队列

asyncio.gather()已经帮你做好了任务调度和结果收集,完全不需要手动维护队列——手动队列反而容易引入锁问题。示例代码如下:

import asyncio
import aiohttp

async def fetch_single_url(session, url, semaphore):
    # 用信号量控制并发数,避免请求过载
    async with semaphore:
        async with session.get(url) as resp:
            # 根据你的需求替换成json()/content()等
            return await resp.text()

async def main(url_list, max_concurrent=10):
    # 初始化信号量控制并发
    semaphore = asyncio.Semaphore(max_concurrent)
    # 复用ClientSession,减少连接建立开销
    async with aiohttp.ClientSession() as session:
        # 批量创建协程任务,用gather统一调度
        tasks = [fetch_single_url(session, url, semaphore) for url in url_list]
        results = await asyncio.gather(*tasks)
        return results

if __name__ == "__main__":
    test_urls = ["https://example.com"] * 50
    results = asyncio.run(main(test_urls))
    print(f"完成{len(results)}个请求")

2. 避免同步锁的结果收集

上面的代码用asyncio.gather()直接获取结果列表,完全不需要加锁——它是协程级别的同步机制,没有线程锁的开销,效率比手动维护加锁容器高太多。

3. 额外优化点:复用连接池

aiohttp.ClientSession会自动维护连接池,避免每个请求都重新建立TCP连接,这能进一步提升请求速度。

四、如何验证你的实现是否正确?

你可以通过这几个小方法确认:

  • 打印每个请求的开始/结束时间,看是否有时间重叠(有重叠才是真并发);
  • 开启asyncio.debug()模式运行,检查是否有未被正确挂起的同步阻塞操作;
  • 对比总耗时:如果是真异步,总耗时应该接近单个请求的耗时(而不是请求数×单个耗时)。

内容的提问来源于stack exchange,提问作者Defenestrator6

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:27:48