Книга Мэттью Фаулера «Asyncio и конкурентное программирование на Python»

Недавно в издательстве ДМК вышла книга Мэттью Фаулера «Asyncio и конкурентное программирование на Python».

Как известно, у интерпретатора Python из-за наличия GIL (Global Interpreter Lock) имеются достаточно серьезные ограничения для полноценного использования всех ядер процессора. Для сложных вычислительных задач можно запускать несколько процессов со своим интерпретатором Python, каждый из которых обрабатывал бы свой собственный кусок данных, но создание нового процесса — достаточно тяжелая операция, и если ее делать часто, то производительность будет проседать. В то же время при некоторых низкоуровневых операциях (в основном это ввод-вывод) возможно написать код, который бы выполнялся параллельно, и именно в этом основная идея асинхронного программирования. Использование асинхронного программирования — это достаточно сложная область в любом языке программирования, а в Python все усложняется еще и тем, что нужно понимать, когда асинхронное программирование может дать какой-то выигрыш.

Книга Мэттью Фаулера рассматривает асинхронное и параллельное программирование (объединенные под одним термином «конкурентное программирование», что показывает аккуратность автора в терминологии) с разных сторон. Здесь будет рассказано и про асинхронщину, и про многопоточноть (в значении multithreading), и про прогопроцессность. Все это в конце концов рассматривается под тем углом, как для всего этого дела может помочь стандартная библиотека asyncio, которая в первую очередь ассоциируется именно с асинхронным программированием, хотя она позволяет использовать также пулы потоков и процессов.

В самом начале книги автор аккуратно разбирает термины «параллелизм», «многозадачность», «конкурентность» и их отличие.

Конкурентность возможна, когда несколько задач может работать независимо друг от друга. Конкурентность можно организовать, имея процессор всего с одним ядром, применив вытесняющую многозадачность… Параллелизм подразумевает конкурентность, но обратное верно не всегда. Многопоточное приложение, работающее на многоядерной машине, является и конкурентным, и параллельным.

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

После этого автор рассказывает про GIL, что это такое, зачем он нужен, почему от него до сих пор не удалось избавиться, и в каких случаях его можно обойти, начав выполнять задачи действительно параллельно (ввод-вывод или низкоуровневый код, написанный на других языках программирования).

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

Например, введение в asyncio начинается с исследования корутин (coroutine, которые в данной версии книги перевели как сопрограммы), рассказывается, чем они отличаются от обычных функций, какой они имеют тип, как их можно запускать с помощью библиотеки asyncio. После этого начинается описание функций asyncio, позволяющих создавать задачу (Task) по сопрограмме (будем использовать терминологию, как в книге), как их запускать и прерывать, как ожидать выполнение задач с указанием времени таймаута.

При этом автор очень дотошно описывает возможные подводные камни каждой функции. Например, что произойдет при возникновении исключения в том или ином месте кода. По ходу описания, автор говорит о том, как тот или иной класс работает «под капотом», чтобы то, что происходит, не казалось какой-то магией. Более того, в последней главе автор сделает несколько классов, которые покажут, что в принципе библиотека asyncio — это только удобный, но не обязательный инструмент для построения асинхронного кода, и при желании можно написать свою реализацию цикла событий и классов для асинхронной работы, и даже покажет использование альтернативы asyncio — библиотеки uvloop.

Одна из первых глав посвящена плавному переводу приложения от синхронной версии до асинхронной. В этой главе создаются два очень компактных приложения: клиент и сервер, взаимодействующие между собой через сокеты. В полностью синхронной версии используются блокирующие сокеты, что не позволяет в однопоточном режиме к одному серверу подключаться нескольким клиентам. После этого автор переводит код на использование неблокирующих сокетов, которые уже позволят взаимодействовать с несколькими клиентами, но ценой 100%-й нагрузки на одно ядро процессора (больше использовать пока не можем). Затем автор для слежения за сокетами начинает использовать низкоуровневую библиотеку selectors, которая позволяет коду засыпать и пробуждаться только в момент появления событий — передачи данных по сокету. После чего автор говорит, что, в принципе, то, что мы сделали, уже реализовано в asyncio, и давайте перепишем код, чтобы использовать эту замечательную библиотеку.

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

Одна из глав посвящена асинхронному взаимодействию с веб-серверами с использованием библиотеки aiohttp. В другой главе рассказывается о том, как можно асинхронно работать с базой данных PostgreSQL с использованием библиотеки asyncpg. Здесь рассказывается о том, как создавать пул подключений для экономии времени подключения к базе данных, как посылать простые запросы и работать с транзакциями.

Не обошел автор стороной и такие задачи, для которых асинхронщина не подходит — тяжелые счетные задачи. Целая глава книге посвящена многопроцессной обработке данных, стандартной библиотеке multiprocessing и демонстрации того, как для запуска процессов можно использовать все ту же asyncio.

Следом за многопроцесностью автор рассказывает о многопоточности. Возможности потоков в Python ограничены, но часто они тоже выручают. Для использования пула потоков тоже можно использовать библиотеку asyncio. Здесь же заодно рассказывается об инструментах синхронизации потоков. В качестве примера в этой главе создается небольшое приложение с GUI на основе Tkinter, в котором при нажати на кнопку происходит многократный запрос к веб-серверу, при этом асинхронный цикл событий запускается в отдельном потоке, чтобы не мешать работе основного потока, отвечающего за отрисовку и обновление окна интерфейса. Под конец главы буквально на нескольких страничках говорится о том, что во многих случаях библиотеку Numpy можно безопасно использовать из нескольких потоков, хотя документация об этом умалчивает.

Спустя несколько глав автор расскажет об инструментах синхронизации, имеющихся в библиотеке asyncio — это блокировки (Lock), семафоры, события (Event) и условия (Condition). Несмотря на однопоточную природу Python, автор приведет примеры, где эти инструменты могут понадобиться. После этого автор расскажет об еще нескольких интересных сущностях, реализованных в asyncio — это асинхронные очереди (с приоритетом и без).

Еще одна глава посвящена асинхронным веб-приложениям, а точнее бэкенду. В этой главе сначала строится простой веб-сервер на основе уже упомянутой выше библиотеки aiohttp, а затем для его запуска используется асинхронный шлюз ASGI, который постепенно приходит на смену WSGI. И в этой главе заодно рассказывается, чем они отличаются. Под конец этой главы приводятся еще два примера, один с использованием асинхронного фреймворка Starlette, а потом не совсем асинхронного Django.

После этого логичным шагом повествования становится глава про микросервисы и их взаимодействие между собой. Здесь тоже используется aiohttp и asyncio. Помимо демонстрации работы aiohttp, здесь показывается реализация паттерна circuit breaker, позволяющий сразу выдавать пользователю ошибку, если какой-то из сервисов был недоступен какой-то время.

Завершается все двумя относительно хардкорными главами (особенно последняя), одна из них посвящена посвящена взаимодействию запуску из Python сторонних приложений в виде подпроцессов и взаимодействию и ними (передача данных в запущенный подпроцесс, когда он ожидает ввод через stdin и получение результата из stdout), а вторая глава посвящена работе цикла событий asyncio «под капотом». Именно в последней главе автор покажет, как можно НЕ использовать asyncio, если вы считаете, что сможете сделать лучше.

Итого. На самом деле книга отличная. В ней все, как мне нравится — сложные вещи поясняются компактными примерами, которые потом уже могут обрастать «жиром» для корректной обработки исключений, добавления каких-то дополнительных возможностей и т.п. Книга написана для опытных программистов, и ее точно не стоит рассматривать как одну из первых книг, которую вы будете читать про Python. После прочтения этой книги я понял, что старое издание книги Лучано Рамальо «Python. К вершинам мастерства» действительно устарела. Там тоже рассказывалось про асинхронное программирование, но с точки зрения создания асинхронных генераторов. Фаулер пишет об этой возможности, но говорит, что так больше делать надо, эта возможность считается устаревшей.

В данной книге автор ориентируется на Python 3.9 и иногда упоминает Python 3.10, который на момент написания книги еще не был выпущен.

Как вы уже поняли, книга мне понравилась. Несмотря на достаточно сложную тему, описано все достаточно понятно. К сожалению, в книге попадаются ошибки, часть из них связаны с русским переводом (несогласованные окончания, опечатки). Что касается второй группы ошибок, то надо смотреть, были ли они в оригинале. Где-то в коде попадались неправильные отступы, где-то неправильный импорт класса, где-то 8 + 4 = 14. В целом не критично, но надо это иметь в виду.

Если не обращать внимание на ошибки и опечатки, то книга заслуживает твердую пятерку.

PS. Вы можете подписаться на новости сайта через RSS, Группу Вконтакте или Канал в Telegram.

Пожалуйста, оцените запись

УжасноПлохоТак себеХорошоОтлично (Количество голосов: 3, средняя оценка: 5,00)
Загрузка...

Leave a comment

Subscribe without commenting