Ответ
Асинхронный event loop в Python работает в одном потоке и блокируется длительными, ресурсоемкими (CPU-bound) вычислениями. Чтобы этого избежать, такие задачи необходимо выносить в отдельный процесс.
Основной способ — использовать run_in_executor() с concurrent.futures.ProcessPoolExecutor.
Пример:
import asyncio
from concurrent.futures import ProcessPoolExecutor
import time
def cpu_bound_operation(n):
"""Имитация тяжелой вычислительной задачи."""
count = 0
for i in range(n):
count += i
return count
async def main():
loop = asyncio.get_running_loop()
# Создаем пул процессов для выполнения CPU-bound задач
with ProcessPoolExecutor() as pool:
# Выносим блокирующую функцию в другой процесс,
# не замораживая event loop.
result = await loop.run_in_executor(
pool, cpu_bound_operation, 10**7
)
print(f"Результат вычислений: {result}")
# Запускаем асинхронную программу
asyncio.run(main())
Ключевые моменты:
ProcessPoolExecutorзапускает задачу в отдельном процессе, используя все доступные ядра CPU и не блокируя основной процесс с event loop.loop.run_in_executor()"оборачивает" блокирующий вызов вawaitableобъект, позволяя event loop продолжать обрабатывать другие задачи, пока вычисления выполняются в фоне.- Для I/O-bound задач (ожидание сети, диска) этот подход избыточен. Достаточно стандартных
async/awaitи библиотек вродеaiohttp, так как event loop эффективно переключается между ними во время ожидания. - Альтернатива для потоков: Для блокирующих, но не ресурсоемких задач (например, вызов старой синхронной I/O-библиотеки), можно использовать
ThreadPoolExecutorили, начиная с Python 3.9, более удобныйasyncio.to_thread().
Ответ 18+ 🔞
Да ты посмотри на эту дичь, блядь! Event loop в Python, сука, работает в одном потоке, а тут ты ему подсовываешь какую-нибудь ебучую задачу, которая процессор жрёт, как не в себя. Он, бедолага, на ней и встанет колом, весь твой асинхронный рай нахуй!
Чтобы такого пиздеца не было, эти вычислительные монстры надо выпинывать в отдельный процесс. Как? Да через run_in_executor() с ProcessPoolExecutor, вот как!
Смотри, вот тебе код, не трогай его, он святой:
import asyncio
from concurrent.futures import ProcessPoolExecutor
import time
def cpu_bound_operation(n):
"""Имитация тяжелой вычислительной задачи."""
count = 0
for i in range(n):
count += i
return count
async def main():
loop = asyncio.get_running_loop()
# Создаем пул процессов для выполнения CPU-bound задач
with ProcessPoolExecutor() as pool:
# Выносим блокирующую функцию в другой процесс,
# не замораживая event loop.
result = await loop.run_in_executor(
pool, cpu_bound_operation, 10**7
)
print(f"Результат вычислений: {result}")
# Запускаем асинхронную программу
asyncio.run(main())
Что тут, блядь, происходит, разжёвываю:
ProcessPoolExecutor— это как отдельная камера пыток для твоей задачи. Он её запихивает в другой процесс, где та может ебашить все ядра процессора, а твой главный event loop в это время спокойно чай пьёт и другие задачи обрабатывает.loop.run_in_executor()— это такой хитрожопый переводчик. Он берёт твою тупую блокирующую функцию и делает из неёawaitableштуку. Event loop говорит: "Ага, ты там в другом процессе работаешь? Ну ок, я пока другими делами займусь, доложишь по готовности".- Для I/O-bound задач (когда ты ждёшь ответа от сети или диска) — это овердохуища. Не надо тут процессы городить. Хватит обычных
async/awaitи специальных библиотек типаaiohttp. Event loop и так отлично переключается между ними, пока они ждут. - Альтернатива для потоков: Если задача не жрёт процессор, а просто тупо блокируется на какой-нибудь старой библиотеке (I/O-операции), можно использовать
ThreadPoolExecutor. Или, если у тебя Python 3.9+, естьasyncio.to_thread()— вообще красота, в рот меня чих-пых!
Вот и вся магия, ёпта. Не заставляй event loop страдать, выноси говнокод в отдельные процессы, и будет всем счастье.