为什么asyncio不总是使用执行器?
我必须发送很多HTTP请求,一旦它们全部返回,程序就可以继续。听起来像是完美的搭配asyncio
。天真地,我将to的调用包装requests
在一个async
函数中,然后将它们交给asyncio
。这行不通。
在网上搜索后,我发现了两种解决方案:
- 使用像aiohttp这样的库,该库可以与
asyncio
- 将阻塞代码包装为
run_in_executor
为了更好地理解这一点,我编写了一个小型基准。服务器端是一个Flask程序,它在响应请求之前等待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
框架一样快。协程和任务的文档并没有真正提到这一点。仅当您阅读loop.run_in_executor()时,它才会说:
# File operations (such as logging) can block the # event loop: run them in a thread pool.
我对此行为感到惊讶。异步的目的是加快阻塞io调用。为什么需要一个额外的包装器run_in_executor
来做到这一点?
的整个卖点aiohttp
似乎是对的支持asyncio
。但据我所知,requests
只要您将其包装在执行程序中,该模块就可以完美运行。是否有理由避免将某些东西包装在执行器中?
-
但据我所知,requests模块可以完美地工作-只要将其包装在执行程序中即可。是否有理由避免将某些东西包装在执行器中?
在executor中运行代码意味着要在OS线程中运行它。
aiohttp
和类似的库允许仅使用协程在没有OS线程的情况下运行非阻塞代码。如果您没有太多工作,则OS线程和协程之间的差异并不明显,尤其是与瓶颈-I /
O操作相比。但是,一旦您做了很多工作,您会注意到由于上下文切换成本高昂,OS线程的性能相对较差。例如,当我将代码更改为
time.sleep(0.001)
和时range(100)
,我的机器将显示:asynchronous (executor) took 0.21461606299999997 seconds aiohttp took 0.12484742700000007 seconds
而且这种差异只会根据请求数而增加。
异步的目的是加快阻塞io调用。
否,目的
asyncio
是提供一种方便的方式来控制执行流程。asyncio
允许您根据协程和OS线程(使用执行程序时)或基于纯粹的协程(像aiohttp
这样)来选择流的工作方式。它
aiohttp
的目的是加快东西,并将它与任务科佩斯如上图所示:)