В каких случаях один поток или исполнители (использующие потоки) превышают asyncio
?
По мере того, как мой опыт использования Python (CPython) прогрессировал, он сосредоточился вокруг оптимизации сценариев на работе для выполнения какой-либо формы вызова веб-сервисов массовым способом и обработки ответов. Однако, после нескольких поколений сценариев, я задаюсь вопросом, почему бы мне не использовать последние версии?
Позвольте мне предоставить контекст ниже ...
Проблема: Запрос N файлов с сервера A на клиент B, обработка и сохранение на диск.
Последовательная
- Создание контейнера запросов, отправка одного запроса, обработка ответа, повторение до завершения
- Вероятно, считается подходом "стандарт / новичок", поскольку он интуитивно достигнут
многопоточных
- Снова построить контейнер, но одновременно отправлять несколько запросов
- Использование семафора для ограничения активных соединений
- Использование очереди для обмена между работниками и получения ответов дампа
- Пусть основной поток обрабатывает ответы
- В сущности, работники работают по принципу "забей и забудь", и основные циклы выполняются в цикле проверки очереди данных
- Обеспечивает отделение проблем от основного, который обрабатывает только данные
ThreadPoolExecutor
- По существу, как решение 2, за исключением гораздо меньшего количества строк кода
- Обоснование: «Я хотел бы иметь возможность обработать ответ, как только он станет доступен»
- Явное создание экземпляров очереди и семафора не требуется
- Если не ошибаюсь, использование структуры Queue and Thread используется в
as_completed()
- В значительной степени обрисовано в общих чертах здесь
asyncio
- Введите здесь серьезную путаницу, но концепция в основном понятна
- Работает в одном потоке в отличие от решений 2 и 3
- Ближе (очень) к решению 3 в реализации, кроме записи на диск
- Требуется использование компонента Solution 3 для сохранения на диск с помощью
run_in_executor()
Таким образом, мы подошли к моей нынешней дилемме: с чего бы мне не хотеть использовать asyncio
для работы, связанной с вводом / выводом?
Асинхронное программирование является концепцией, очень похожей на ООП, и в документах по Решению 3 даже говорится, что «асинхронное выполнение может выполняться с потоками». Но если я могу добиться асинхронного выполнения в одном потоке (исключая дополнительный поток для блокировки ввода-вывода на диск), зачем мне когда-либо использовать Решения 1-3?
Я знаю, что с учетом GIL многопоточность CPython является неоптимальной; Тем не менее, я не вижу причин, по которым кто-либо мог бы использовать потоки или исполнителей. Я провел немало Googling, чтобы посмотреть, смогу ли я найти хорошую статью, в которой говорится, почему кто-то предпочел бы их использовать, но я нашел только статьи, в которых говорится, почему потоки (и впоследствии исполнители, использующие потоки) плохи: переключение контекста ( GIL / OS), условия гонки, истощение ресурсов и т.д ...
Поскольку CPython не использует потоки для использования преимуществ многоядерных процессоров (я полагаю, это библиотека multiprocessing
), потоки не используются для сложных вычислительных задач; таким образом, ограничивая их операциями, связанными с вводом / выводом, для увеличения производительности. Это, однако, не дает мне достаточных оснований понять, почему потоки или исполнители будут использоваться вместо asyncio
.
Если вы можете сделать все это в одном потоке (может быть, 2-3), зачем продолжать вводить накладные расходы на создание, управление и уничтожение потоков (как явным образом, так и через пул / исполнителя)?