Оптимизирован ли run_in_executor для работы в цикле с сопрограммами? - PullRequest
1 голос
/ 06 марта 2019

Меня поражает, что метод run_in_executor() библиотеки asyncio принадлежит к loop объекту.

В частности, в чем будет разница, если я выберу запускать второй поток рядом с циклом асинхронных событий «обычным» способом, import threading, t = threading.Thread(target=...), t.start()?

Возможно, ответ заключается в том, что при использовании модуля asyncio существуют низкоуровневые оптимизации, которые можно выполнить во время выполнения, если цикл знает о дополнительных потоках?

1 Ответ

2 голосов
/ 06 марта 2019

Вы всегда можете запустить другой поток вручную, но тогда вы несете ответственность за его работу, например, использование очереди.В Python 3 concurrent.futures предусмотрен удобный API для выгрузки задач в пул потоков.Метод submit принимает функцию, передает ее потоку в пуле для ее запуска и немедленно возвращает дескриптор, который предоставит результат (или распространит исключение), когда он будет готов.

run_in_executor - это привнесение этого удобства в asyncio.Обычно вы не должны запускать блокирующий код внутри asyncio - даже такое простое, как time.sleep(), запрещено, потому что оно блокирует весь цикл событий.run_in_executor позволяет вам нарушить это правило.Например:

async def sleep_test():
    loop = asyncio.get_event_loop()
    print('going to sleep')
    await loop.run_in_executor(None, time.sleep, 5)
    #time.sleep(5)
    print('waking up')

asyncio.run(asyncio.gather(sleep_test(), sleep_test()))

Запуск этого кода показывает, что оба экземпляра сопрограммы спят параллельно.Если бы мы использовали time.sleep() напрямую, они бы спали последовательно, потому что сон заблокировал бы цикл событий.

Этот пример, конечно, глуп, потому что есть asyncio.sleep(), который делает всевещь качественно, но она показывает основную идею.Реалистичные варианты использования для run_in_executor включают:

  • выполнение кода с привязкой к ЦП, такого как вычисления с пустым фрагментом, изнутри asyncio
  • вызов устаревшего кода, который еще не был перенесен в asyncio
  • блокирование вызовов, когда неблокирующие API просто недоступны (драйверы баз данных, доступ к файловой системе)
...