PIL и блокировка звонков с помощью asyncio - PullRequest
0 голосов
/ 30 ноября 2018

У меня есть приложение asyncio, которое использует сервер из aiohttp и асинхронные сокеты с asyncio.open_connection()

Мой код содержит некоторые блокирующие вызовы из библиотеки PIL, такие как

Image.save()
Image.resize()
  1. Несмотря на то, что звонки не блокируются слишком много времени, все равно, может ли мой веб-сервер зависнуть, если я использую эти блокирующие звонки?Точнее, возможно ли, что цикл обработки событий пропустит события из-за блокировки кода?
  2. Если да, то чем заменить эти функции, которые интегрируются с asyncio?нет версии asyncio PIL.
  3. В общем, что считается «блокирующим кодом» в asyncio?кроме очевидных операций, таких как сокет, чтение файла и т. д.
    Например, os.path.join() считается нормально?как насчет работы с numpy массивом?

1 Ответ

0 голосов
/ 30 ноября 2018

может ли мой веб-сервер зависнуть, если я использую эти блокирующие вызовы?Точнее, возможно ли, что цикл обработки событий будет пропускать события из-за блокировки кода?

Сервер будет точно зависать на время выполнения функций изображения.Вы не пропустите ни одного события, но вся обработка событий будет отложена на время выполнения функций изображения.

Заморозка цикла событий - плохая ситуация - вам следует избегать этого.

Если да, что является заменой для этих функций, которые интегрируются с asyncio?нет версии asyncio для PIL.

Самый простой и универсальный способ избежать замораживания цикла событий - выполнить функцию блокировки в другом потоке или другом процессе, используя asyncio.run_in_executor .Фрагмент кода показывает, как это сделать, и содержит хорошее объяснение, когда использовать процесс или поток:

def blocking_io():
    # File operations (such as logging) can block the
    # event loop: run them in a thread pool.
    with open('/dev/urandom', 'rb') as f:
        return f.read(100)

def cpu_bound():
    # CPU-bound operations will block the event loop:
    # in general it is preferable to run them in a
    # process pool.
    return sum(i * i for i in range(10 ** 7))

Я только хочу добавить, что пул процессов не всегда может быть хорошим решением для всех операций, связанных с процессором.Если функции работы с изображениями не занимают много времени (или, особенно, если на вашем сервере нет нескольких процессорных ядер), все равно может быть более продуктивно запускать их в потоке.

В общем, чтосчитается «блокирующим кодом» в asyncio?кроме очевидных операций, таких как сокет, чтение файла и т. д. Например, считается ли os.path.join () нормально?как насчет работы с пустым массивом?

Грубо говоря, любая функция блокирует: она на некоторое время блокирует цикл обработки событий.Но многие функции, такие как os.path.join, занимают так мало времени, поэтому они не являются проблемой, и мы не называем их «блокирующими».

Трудно сказать точный предел времени выполнения (и замораживания цикла событий)становится проблемой, особенно учитывая, что это время будет отличаться для разных аппаратных средств.Мой предвзятый совет - если ваш код занимает (или может занять)> 50 мс перед возвратом управления в цикл обработки событий, считайте его блокирующим и используйте run_in_executor.

Upd:

Спасибо, имеет ли смысл использовать один цикл событий (основного потока) и другой поток, который будет добавлять задачи с использованием того же цикла?

Я не уверенчто вы имеете в виду здесь, но я думаю, что нет.Нам нужен другой поток, чтобы выполнить какое-то задание, а не добавлять туда задания.

Мне нужен какой-то способ, чтобы поток информировал основной поток после завершения обработки изображения`

Просто дождитесь результата run_in_executor или начните задание с него.run_in_executor - сопрограмма, выполняющая что-либо в фоновом потоке без блокировки цикла событий.

Это будет выглядеть так:

thrad_pool = ThreadPoolExecutor()


def process_image(img):
    # all stuff to process image here
    img.save()
    img.resize()


async def async_image_process(img):
    await loop.run_in_executor(
        thread_pool, 
        partial(process_image, img)
    )


async def handler(request):

    asyncio.create_task(
        async_image_process(img)
    )
    # we use task to return response immediately,
    # read https://stackoverflow.com/a/37345564/1113207

    return web.Response(text="Image processed without blocking other requests")
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...