Какие варианты ускорения выполнения нескольких IO-bound операций параллельно в Python существуют
Что проверяют
Отличаете ли вы IO-bound от CPU-bound и знаете ли, что на ожидании ввода-вывода GIL отпускается, поэтому потоки здесь как раз работают. Вопрос практический: есть сотня HTTP-запросов, надо не делать их по очереди. Следом спрашивают про ограничение одновременности, таймауты и ошибки.
Как отвечать
- пул потоков:
concurrent.futures.ThreadPoolExecutorсmapилиsubmit. Минимальные правки в синхронном коде, работает с любой блокирующей библиотекой, включаяrequestsи обычные драйверы БД. Цена это память под стек потока и переключения, разумный потолок сотни, а не десятки тысяч; - asyncio:
async def,await,asyncio.gatherилиTaskGroupначиная с 3.11. Один поток тянет тысячи соединений, но неблокирующим должен быть весь стек:aiohttpилиhttpx.AsyncClientвместоrequests,asyncpgвместо синхронного драйвера; - мост между мирами:
asyncio.to_thread(func, *args)для блокирующего вызова внутри корутины,loop.run_in_executorв старом коде; - ограничение одновременности:
asyncio.Semaphore(20)или лимиты пула соединений у клиента. Без этого вы упрётесь в чужие лимиты и получите отказы вместо ускорения; - переиспользование соединений: одна сессия или клиент на приложение, а не новый на каждый запрос. Keep-alive и готовый пул часто дают больше, чем лишние потоки;
- процессы: для чистого ввода-вывода избыточны, но оправданы, когда после загрузки идёт тяжёлый разбор.
Полезно добавить про ошибки и время: asyncio.gather(return_exceptions=True) возвращает исключения как обычные результаты, а TaskGroup при первой ошибке отменяет остальные задачи. Таймаут нужен на каждый запрос (asyncio.timeout или параметр клиента), плюс повтор с экспоненциальной задержкой на временные сбои.
Где спотыкаются
Вызывают time.sleep или requests.get внутри корутины. Цикл событий встаёт целиком, и асинхронность превращается в последовательное выполнение.
Запускают тысячу задач без семафора и вместо ускорения ловят ответ 429 от сервиса или исчерпание файловых дескрипторов.
Тащат multiprocessing под сетевые запросы со ссылкой на GIL. На ожидании GIL не мешает, а процессы добавляют память и сериализацию.