【发布时间】:2019-04-15 02:05:17
【问题描述】:
我必须发送很多 HTTP 请求,一旦它们都返回,程序可以继续。听起来很适合asyncio。有点天真,我将我对requests 的调用封装在一个async 函数中,并将它们交给asyncio。这行不通。
网上搜索后,找到了两种解决方案:
- 使用像aiohttp 这样的库,它可以与
asyncio一起使用 - 将阻塞代码封装在对
run_in_executor的调用中
为了更好地理解这一点,我编写了一个小型基准测试。服务器端是一个烧瓶程序,它在响应请求之前等待 0.1 秒。
from flask import Flask
import time
app = Flask(__name__)
@app.route('/')
def hello_world():
time.sleep(0.1) // heavy calculations here :)
return 'Hello World!'
if __name__ == '__main__':
app.run()
客户是我的基准
import requests
from time import perf_counter, sleep
# this is the baseline, sequential calls to requests.get
start = perf_counter()
for i in range(10):
r = requests.get("http://127.0.0.1:5000/")
stop = perf_counter()
print(f"synchronous took {stop-start} seconds") # 1.062 secs
# now the naive asyncio version
import asyncio
loop = asyncio.get_event_loop()
async def get_response():
r = requests.get("http://127.0.0.1:5000/")
start = perf_counter()
loop.run_until_complete(asyncio.gather(*[get_response() for i in range(10)]))
stop = perf_counter()
print(f"asynchronous took {stop-start} seconds") # 1.049 secs
# the fast asyncio version
start = perf_counter()
loop.run_until_complete(asyncio.gather(
*[loop.run_in_executor(None, requests.get, 'http://127.0.0.1:5000/') for i in range(10)]))
stop = perf_counter()
print(f"asynchronous (executor) took {stop-start} seconds") # 0.122 secs
#finally, aiohttp
import aiohttp
async def get_response(session):
async with session.get("http://127.0.0.1:5000/") as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
await get_response(session)
start = perf_counter()
loop.run_until_complete(asyncio.gather(*[main() for i in range(10)]))
stop = perf_counter()
print(f"aiohttp took {stop-start} seconds") # 0.121 secs
因此,asyncio 的直观实现不会处理阻塞 io 代码。但是如果你正确使用asyncio,它和特殊的aiohttp 框架一样快。 coroutines and tasks 的文档并没有真正提到这一点。只有当你阅读loop.run_in_executor() 时,它才会说:
# File operations (such as logging) can block the # event loop: run them in a thread pool.
我对这种行为感到惊讶。 asyncio 的目的是加速阻塞 io 调用。为什么需要额外的包装器run_in_executor 来执行此操作?
aiohttp 的全部卖点似乎是对asyncio 的支持。但据我所知,requests 模块可以完美运行——只要你将它包装在一个执行器中。是否有理由避免在 executor 中包装一些东西?
【问题讨论】:
-
一般来说,ayncio 的目的不是为了加快速度,而是为了减少延迟。您的两种方法都可以做到这一点,而执行程序可能需要更多资源。
-
执行器是基于线程的。
asyncio使用非阻塞套接字,因此它可以用一个线程请求多个,但requests不是
标签: python python-requests python-asyncio coroutine aiohttp