当前位置: 首页 > 面试题库 >

为什么asyncio不总是使用执行器?

松雅昶
2023-03-14
问题内容

我必须发送很多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的目的是加快东西,并将它与任务科佩斯如上图所示:)



 类似资料:
  • 我在玩Java的可选代码,认为它的工作原理就像一个if else块。但是在下面的代码中,即使变量不是,也会执行块的内容。有什么解释吗? 产出:

  • z-index总是达不到效果,对层这块总是无法用z-index搞定,求指教

  • 问题内容: 我从迁移到了,我找不到等效的。(A有两个参数:要运行的函数和两次调用之间的毫秒数。) 有这样的等同物吗? 如果没有,那么在没有冒一会儿风险的情况下实现此目标的最干净方法是什么? 问题答案: 对于3.5以下的Python版本: 对于Python 3.5及更高版本:

  • 问题内容: 我正在学习Go,并且想尝试goroutine和频道。 这是我的代码: 结果如下: 我不明白为什么我的goroutine永远不会执行。没有输入“进入goroutine”,并且没有任何错误消息。 问题答案: 事实是您的goroutine开始执行,但是在执行任何操作之前就结束了,因为您的程序在打印后立即停止:goroutine的执行与主程序无关,但是将在与程序相同的位置处停止。因此,基本上,

  • 问题内容: 我在MacOSX上使用Docker(带有Boot2Docker)。 我可以从Docker Hub运行映像。 但是,当我尝试像这样运行自己的映像之一时: 要么 要么 我得到: 我猜它找不到在容器中执行的bash二进制文件,但是为什么呢? 基本图像是 谢谢你的帮助。 阿什莉 问题答案: 您的图片基于不带bash外壳的busybox。它的确有外壳。 所以这不起作用: 但这确实是: 由于您的入

  • 我不断得到: debug.log 我的包文件如下: 我是npm的新手,当我第一次下载它时,我能够在我的本地服务器上毫无问题地启动它。 我想切换我的浏览器,这样做,突然npm start有一个问题。 我知道还有其他类似的问题,但没有一个能缓解我的问题。

  • 问题内容: 考虑到此代码,我是否可以绝对确定该块始终执行,无论它是什么? 问题答案: 是的,将在执行或代码块后调用。 唯一不会被调用的时间是: 如果你调用 如果你调用 如果JVM首先崩溃 如果JVM在或catch块中达到了无限循环(或其他不间断,不终止的语句) 操作系统是否强行终止了JVM进程;例如,在UNIX上 如果主机系统死机;例如,电源故障,硬件错误,操作系统崩溃等 如果该块将由守护程序线程

  • 我正在用Java开发一个俄罗斯方块克隆,在我想要清除整行并删除上面的所有内容之前,一切似乎都正常工作。虽然我所有的数据都正确地表示了转换,但我的paintComponent方法似乎只清除了行,但上面显示的所有内容都保持在repaint()调用之前的状态。新的碎片将穿过幻影积木,落在最下面一排的隐形积木上,上面的碎片会落在那里。 这是我的油漆成分方法: 这是计时器侦听器中actionPerforme