Каково конструктивное поведение, когда os.fork() вызывается в цикле asyncio?

#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.