如果使用锁,为什么 aiohttp 请求会卡住?
Why the aiohttp request stucks if lock is used?
为什么这个代码:
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的请求没有被执行?与线程相同(已评论)尝试请求,工作。
发生这种情况是因为您将阻塞整个执行线程的同步锁与 asyncio
混合使用,后者要求所有操作都是非阻塞的。您的两个协程(对 test
的两次调用)都在同一个线程中 运行,因此当第二个协程尝试获取锁但被阻塞时,它也会阻塞第一个协程(持有锁) 无法取得任何额外进展。
您可以改用 asyncio.Lock
来解决这个问题。它只会阻塞等待锁的协程,而不是阻塞整个线程。但是请注意,此锁不能在进程之间传递,因此除非您停止使用 multiprocessing
,否则它不会起作用,这在上面的示例代码中实际上不是必需的。您只需创建一个仅在单个子进程中使用的锁,因此您可以简单地在子进程中创建 asyncio.Lock
而不会损失任何功能。
但是,如果您的实际用例需要一个 asyncio
友好的锁,该锁也可以在进程之间共享,您可以为此使用 aioprocessing
(完全披露:我是 aioprocessing
).
为什么这个代码:
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的请求没有被执行?与线程相同(已评论)尝试请求,工作。
发生这种情况是因为您将阻塞整个执行线程的同步锁与 asyncio
混合使用,后者要求所有操作都是非阻塞的。您的两个协程(对 test
的两次调用)都在同一个线程中 运行,因此当第二个协程尝试获取锁但被阻塞时,它也会阻塞第一个协程(持有锁) 无法取得任何额外进展。
您可以改用 asyncio.Lock
来解决这个问题。它只会阻塞等待锁的协程,而不是阻塞整个线程。但是请注意,此锁不能在进程之间传递,因此除非您停止使用 multiprocessing
,否则它不会起作用,这在上面的示例代码中实际上不是必需的。您只需创建一个仅在单个子进程中使用的锁,因此您可以简单地在子进程中创建 asyncio.Lock
而不会损失任何功能。
但是,如果您的实际用例需要一个 asyncio
友好的锁,该锁也可以在进程之间共享,您可以为此使用 aioprocessing
(完全披露:我是 aioprocessing
).