如果使用锁,为啥 aiohttp 请求会卡住?

Posted

技术标签:

【中文标题】如果使用锁,为啥 aiohttp 请求会卡住?【英文标题】:Why the aiohttp request stucks if lock is used?如果使用锁,为什么 aiohttp 请求会卡住? 【发布时间】:2021-12-21 12:47:01 【问题描述】:

为什么会有这段代码:

import asyncio
import time
from multiprocessing import Pool, Manager
from threading import Thread, Lock

from aiohttp import ClientSession


async def test(s: ClientSession, lock: Lock, identifier):
    print(f'before acquiring identifier')
    lock.acquire()
    print(f'before request identifier')
    async with s.get('http://icanhazip.com') as r:
        print(f'after request identifier')
    lock.release()
    print(f'after releasing identifier')


async def main(lock: Lock):
    async with ClientSession() as s:
        await asyncio.gather(test(s, lock, 1), test(s, lock, 2))


def run(lock: Lock):
    asyncio.run(main(lock))


if __name__ == '__main__':
    # Thread(target=run, args=[Lock()]).start()
    with Pool(processes=1) as pool:
        pool.map(run, [Manager().Lock()])

打印:

before acquiring 1
before request 1
before acquiring 2

然后卡住了?为什么没有执行标识符为 1 的请求?与 Thread 相同(已注释)已尝试请求,工作正常。

【问题讨论】:

【参考方案1】:

发生这种情况是因为您将同步锁与asyncio 混合使用,同步锁会阻塞整个执行线程,这要求所有操作都是非阻塞的。您的两个协程(对test 的两个调用)都在同一个线程中运行,因此当第二个协程尝试获取锁但被阻塞时,它也会阻止第一个协程(持有锁)进行任何操作其他进展。

您可以改用asyncio.Lock 来解决此问题。它只会阻塞等待锁的协程,而不是阻塞整个线程。请注意,此锁不能在进程之间传递,因此除非您停止使用multiprocessing,否则它不会起作用,这在上面的示例代码中实际上不是必需的。您只需创建一个仅在单个子进程中使用的锁,因此您可以在子进程中简单地创建 asyncio.Lock 而不会丢失任何功能。

但是,如果您的实际用例需要 asyncio 友好的锁,该锁也可以在进程之间共享,您可以使用 aioprocessing 来实现这一点(完全披露:我是 aioprocessing 的作者)。

【讨论】:

以上是关于如果使用锁,为啥 aiohttp 请求会卡住?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 aiohttp 比 gevent 慢得多?

为啥我的进程列表在运行 aiohttp 时会显示多个线程?

aiohttp的安装

即使使用 asyncio 和 aiohttp,方法也会等待请求响应

aiohttp 异步http请求-12.aiohttp 请求生命周期(和requests库有什么不一样?)

python asyncio 异步 I/O - 实现并发http请求(asyncio + aiohttp)