Сравнение асинхронного программирования и потоков#

В Python есть три основных способа запускать задачи «параллельно»:

  • процессы

  • потоки

  • асинхронное программирование

Примечание

Полноценно параллельная работа будет только при использовании процессов, по всех остальных случая речь идет о попеременном выполнении (concurrent).

Модули, которые позволяют запустить задачи:

  • в процессах: multiprocessing, concurrent.futures

  • в потоках: threading, concurrent.futures

  • асинхронно: asyncio и масса других вариантов и сторонних фреймворков

Примечание

Для упрощения я буду говорить о запуске функций в потоках/процессах, но технически это может быть любой вызываемый объект. Аналогично дальше я в основном пишу asyncio, но почти все из сказанного про asyncio относится и к другим вариантам асихронного программирования.

Запуск функций в процессах это единственный вариант из перечисленных выше, который позволит запустить код на разных ядрах. При этом, когда речь идет о количестве процессов это единицы-десятки. Если задача завязана на CPU это единственный способ как-то распараллелить задачу.

Потоки в CPython выполняются не параллельно, а попеременно из-за GIL. Поэтому преимущества при использовании потоков будут только для задач, которые завязаны на ввод-вывод. Например, в книге по основам Python мы рассматривали именно потоки, потому что подключение к оборудованию это задача завязанная на операции ввода-вывода. Количество параллельных потоков может быть десятки-сотни.

Асинхронное программирование это еще один вариант выполнения задач попеременно. В Python есть много способов реализации асинхронности, но в книге рассматривается только один - asyncio. Как подсказывает название, asyncio также предназначен для задач завязанных на операции ввода-вывода (IO). В этом подходе все выполняется в одном потоке, но при этом можно делать тысячи параллельных подключений.

Примечание

Для знакомства с другими вариантами реализации асинхронности очень рекомендую послушать Олега Молчанова.

Часто можно встретить фразу, что асинхронный код проще чем многопоточный. Тут имеется в виду, что в асинхронном коде очень четко видно те места, где будет переключение, а весь остальной код будет выполняться последовательно без прерывания. В то время как многопоточный код может быть прерван в любом месте и часто это приводит к проблемам.

При этом разобраться с тем как работает asyncio совсем не просто. К тому же, в большинстве случаев не получится использовать уже известные модули, например, netmiko, а вместо них надо будет использовать асинхронный аналог.

Самые большие преимущества при использовании asyncio можно получить, например, в веб. Тут подключений действительно может быть тысячи и с asynio не надо будет создавать поток для каждого подключения. Однако для работы с сетевым оборудованием asyncio тоже может быть полезен и во многих случаях, особенно когда подключений много, работать быстрее многопоточного варианта.

При этом конечно надо учитывать, что почти все модули, которые работали в многопоточном варианте, не будут работать с asyncio, так как они будут блокировать работу и не будут отдавать управление циклу событий.

Отличия asyncio от многопоточной работы:

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

  • при работе с asyncio, мы явно указываем в каком месте надо сделать переключение (await)

  • при использовании asyncio работа идет в одном потоке

  • в потоках будет работать почти любой из распространенных модулей

  • для работы с asyncio, как правило, надо будет искать альтернативные модули