Почему Asyncio не всегда использует исполнителей?
Я должен отправить много 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
фреймворк. Документы для сопрограмм и задач на самом деле не упоминают об этом. Только если вы читаете в loop.run_in_executor (), он говорит:
# File operations (such as logging) can block the # event loop: run them in a thread pool.
Я был удивлен таким поведением. Цель asyncio - ускорить блокировку вызовов. Почему дополнительная обертка, run_in_executor
необходимо сделать это?
Весь смысл продажи aiohttp
кажется, поддержка asyncio
, Но, насколько я понимаю, requests
Модуль работает отлично - до тех пор, пока вы его заверните в исполнителя. Есть ли причина избегать того, чтобы что-то оборачивать в исполнителя?
1 ответ
Но, насколько я вижу, модуль запросов работает отлично - до тех пор, пока вы заключаете его в исполнителя. Есть ли причина избегать того, чтобы что-то оборачивать в исполнителя?
Запуск кода в executor означает запуск его в потоках ОС.
aiohttp
и подобные библиотеки позволяют запускать неблокирующий код без потоков ОС, используя только сопрограммы.
Если у вас мало работы, разница между потоками ОС и сопрограммами незначительна, особенно по сравнению с операциями ввода-вывода с узким местом. Но когда у вас много работы, вы можете заметить, что потоки ОС работают относительно хуже из-за дорогостоящего переключения контекста.
Например, когда я изменяю ваш код на time.sleep(0.001)
а также range(100)
Моя машина показывает:
asynchronous (executor) took 0.21461606299999997 seconds
aiohttp took 0.12484742700000007 seconds
И эта разница будет только увеличиваться в зависимости от количества запросов.
Цель asyncio - ускорить блокировку вызовов.
Нет, цель asyncio
это предоставить удобный способ контролировать выполнение потока. asyncio
позволяет выбрать, как работает поток - на основе сопрограмм и потоков ОС (когда вы используете executor) или на чистых сопрограммах (например, aiohttp
делает).
Это aiohttp
Цель ускорить процесс, и он справляется с задачей, как показано выше:)