#python #fork #python-asyncio #event-loop
#python #fork #python-asyncio #цикл событий
Вопрос:
Работает ли asyncio с os.fork()?
Фрагмент кода 1:
import asyncio
import os
import aiohttp
async def main():
url = "https://google.com"
pid = os.fork()
if pid == 0:
# child process
print("in child process")
await fetch(url)
print("in child process done")
else:
print("in parent process")
await asyncio.sleep(20)
print("in parent process done")
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
if __name__ == "__main__":
asyncio.run(main())
Приведенный выше код работает нормально.
Фрагмент кода 2:
import asyncio
import os
import aiohttp
async def main():
url = "https://google.com"
pid = os.fork()
if pid == 0:
# child process
print("in child process")
await asyncio.sleep(10) # different with code snippet 1
# await fetch(url)
print("in child process done")
else:
print("in parent process")
await asyncio.sleep(20)
print("in parent process done")
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
if __name__ == "__main__":
asyncio.run(main())
Приведенный выше код вызовет следующее исключение:
Traceback (most recent call last):
File "fork_sleep.py", line 28, in <module>
asyncio.run(main())
File "/usr/lib/python3.8/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "fork_sleep.py", line 13, in main
await asyncio.sleep(10) # different with code snippet 1
File "/usr/lib/python3.8/asyncio/tasks.py", line 637, in sleep
loop = events.get_running_loop()
RuntimeError: no running event loop
Причина исключения «цикл событий не выполняется» заключается в том, что функция get_running_loop сравнивает os.getpid() и pid, сохраненный в цикле. Когда они отличаются, возникает указанное выше исключение.
Пожалуйста, обратитесь к следующему коду из исходного кода cpython.
def get_running_loop():
"""Return the running event loop. Raise a RuntimeError if there is none.
This function is thread-specific.
"""
# NOTE: this function is implemented in C (see _asynciomodule.c)
loop = _get_running_loop()
if loop is None:
raise RuntimeError('no running event loop')
return loop
def _get_running_loop():
"""Return the running event loop or None.
This is a low-level function intended to be used by event loops.
This function is thread-specific.
"""
# NOTE: this function is implemented in C (see _asynciomodule.c)
running_loop, pid = _running_loop.loop_pid
if running_loop is not None and pid == os.getpid():
return running_loop
Итак, кажется, что цикл событий asyncio отлично работает в дочернем процессе, если вы не касаетесь функции get_running_loop.
Мой вопрос в том, каково поведение по умолчанию? Почему автор проверяет pid в функции _get_running_loop? И каково решение, если вы столкнулись с «не запущенным циклом событий» в дочернем процессе.
Комментарии:
1. Даже если бы это сработало, я не думаю, что у asyncio есть шанс работать в следующем дочернем процессе
fork()
. Asyncio использует пул потоков дляrun_in_executor
, и пул потоков, безусловно, не будет работать после того,fork()
какfork()
разветвляется только один поток, а не все потоки, поэтому количество потоков, которые, по мнению пула потоков, он контролирует, и количество потоков, которыми он фактически управляет, будут отличаться.2. Затем существуют различные файловые дескрипторы, которые цикл событий использует внутренне, такие как дескриптор пробуждения для
call_soon_threadsafe
и друзей, а также другой дескриптор пробуждения, используемый для обработки сигналов — все они будут совместно использоваться дочерним процессом и вызывать помехи. Единственный безопасный способ использовать asyncio в разветвленном процессе — это повторноexec
запустить Python и указать ему запускать asyncio.