Event loop и конкурентность
Тема дорожной карты · Python Programming
Цикл событий (event loop) в Python является ключевым компонентом для организации конкурентного выполнения задач. Он планирует и управляет выполнением корутиных функций, которые могут быть приостановлены и возобновлены в зависимости от готовности I/O или таймера. Это позволяет эффективно использовать ресурсы и обеспечивает конкурентное выполнение задач, что особенно важно для I/O-ограниченных приложений.
Предпочтительнее использовать высокоуровневые помощники, такие как asyncio.run, asyncio.gather и asyncio.TaskGroup (с версии 3.11), для структурированного управления конкурентностью, вместо ручного управления циклом событий. Это помогает избежать ошибок и упрощает код. Важно сохранять ссылки на Task для управления жизненным циклом задач, чтобы избежать случайного удаления задач сборщиком мусора. Для отмены задачи следует использовать кооперативный подход, освобождая ресурсы в блоке finally или через asyncio.shield.
Как это работает
Event loop и конкурентность основываются на модуле asyncio, который был добавлен в Python с версии 3.4. Этот модуль предоставляет однопоточный цикл событий для выполнения корутиных функций, определенных с помощью ключевого слова async def. Когда корутина встречает ключевое слово await, выполнение приостанавливается до тех пор, пока не будет завершено ожидаемое событие. Цикл событий автоматически переключается на другую готовую корутину, что позволяет эффективно использовать ресурсы и обеспечивает конкурентное выполнение задач.
Tasks в asyncio создаются с помощью функции asyncio.create_task, что позволяет конкурентно запускать корутины. Функция asyncio.gather позволяет ждать завершения нескольких задач параллельно, что существенно упрощает управление конкурентными операциями. Асинхронные библиотеки, такие как aiohttp, asyncpg, и httpx, заменяют блокирующие вызовы, обеспечивая асинхронный доступ к сетевым ресурсам и базам данных.
Когда применять
Асинхронное программирование особенно полезно, когда нагрузка на систему ограничена I/O. Это включает в себя тысячи HTTP-запросов, WebSocket fan-out, скрейпинг, асинхронный доступ к базам данных в веб-фреймворках, таких как FastAPI. Однако, для задач, которые ограничены вычислительной мощностью (CPU-bound), асинхронное программирование не поможет. В таких случаях следует использовать модуль multiprocessing или внешние воркеры для распределения нагрузки.
Для простых линейных скриптов с небольшим количеством HTTP-вызовов синхронный код с использованием библиотеки requests может быть короче и проще для отладки. Однако, при наличии более чем 50 конкурентных операций, асинхронное программирование становится более эффективным и позволяет лучше использовать ресурсы системы.
Типичные ошибки
Одна из распространенных ошибок при использовании цикла событий и конкурентности заключается в смешении синхронных блокирующих вызовов в асинхронном коде. Например, вызов time.sleep(1) замораживает весь цикл событий, что может привести к нежелательной блокировке системы. Другой распространенной ошибкой является запуск неограниченного количества задач без механизма обратного давления, что может привести к быстрому исчерпанию памяти. Также важно избегать забытого await, что может привести к получению корутины-объекта, который никогда не будет запущен. Начиная с Python 3.11, интерпретатор предупреждает об этой ошибке.