Асинхронная концепция программирования заключается в том, что результат
выполнения функции доступен не сразу же, а через некоторое время в виде
некоторого асинхронного (нарушающего обычный порядок выполнения) вызова. Зачем
такое может быть полезно? Рассмотрим несколько примеров.
Первый пример – сетевой сервер, веб-приложение. Чаще всего как таковых
вычислений на процессоре такие приложения не выполняют. Большая часть времени
(реального, не процессорного) тратится на ввод-вывод: чтение запроса от
клиента, обращение к диску за данными, сетевые обращение к другим подсистемам
(БД, кэширующие сервера, RPC и т.п.), запись ответа клиенту. Во время этих
операций ввода-вывода процессор простаивает, его можно загрузить обработкой
запросов других клиентов. Возможны различные способы решить эту задачу: отдельный
процесс на каждое соединение (Apache mpm_prefork, PostgreSQL, PHP FastCGI),
отдельный поток (нить) на каждое соединение или комбинированный вариант
процесс/нить (Apache mpm_worker, MySQL). Подход с использованием процессов или
нитей перекладывает мультиплексирование процессора между обрабатываемыми
соединениями на ОС, при этом расходуется относительно много ресурсов (память,
переключения контекста и т.п.), такой вариант не подходит для обработки
большого количества одновременных соединений, но идеален для ситуации, когда
объем вычислений достаточно высок (например, в СУБД). К плюсам модели нитей и
процессов можно добавить потенциальное использование всех доступных процессоров
в многопроцессорной архитектуре.
Альтернативой является использование однопоточной модели с использованием
примитивов асинхронного ввода-вывода, предоставляемых ОС (select, poll, и
т.п.). При этом объем ресурсов на каждое новое обслуживаемое соединение не
такой большой (новый сокет, какие-то структуры в памяти приложения). Однако
программирование существенно усложняется, т.к. данные из сетевых сокетов
поступают некоторыми «отрывками», причем за один цикл обработки данные поступают от разных
соединений, находящихся в разных состояниях, часть соединений могут быть
входящими от клиентов, часть – исходящими к внешним ресурсам (БД, другой сервер
и т.п.). Для упрощения разработки используются различные концепции: callback,
конечные автоматы и другие. Примеры сетевых серверов, использующих асинхронный
ввод-вывод: nginx, lighttpd,
HAProxy, pgBouncer, и т.д. Именно при такой
однопоточной модели возникает необходимость в асинхронном программировании.
Например, мы хотим выполнить запрос в БД. С точки зрения программы выполнение
запроса – это сетевой ввод-вывод: соединение с сервером, отправка запроса,
ожидание ответа, чтение ответа сервера БД. Поэтому если мы вызываем
функцию «выполнить запрос БД», то она сразу вернуть результат не сможет
(иначе она должна была бы заблокироваться), а вернет лишь нечто, что позволит
впоследствие получить результат запроса или, возможно, ошибку (нет соединения
с сервером, некорректный запрос и т.п.) Этим возвращаемым значением удобно
сделать именно Deferred.
Второй пример связан с разработкой обычных десктопных приложений. Предположим,
мы решили сделать аналог Miranda (QIP, MDC, …), то есть свой мессенджер.
В интерфейсе программы есть контакт-лист, где можно удалить контакт. Когда
пользователь выбирает это действие, он ожидает что контакт исчезнет на экране
и что он действительно удалится из контакт-листа. На самом деле операция
удаления из серверного контакт-листа опирается на сетевое взаимодействие
с сервером, при этом пользовательский интерфейс не должен быть заблокирован
на время выполнения этой операции, поэтому в любом случае после выполнения
операции потребуется некоторое асинхронное взаимодействие с результатом операции.
Можно использовать механизм сигналов-слотов, callback’ов или что-то еще,
но лучше всего подойдет Deferred: операция удаления из контакт-листа возвращает
Deferred, в котором обратно придет либо положительный результат (всё хорошо),
либо исключение (точная ошибка, которую надо сообщить пользователю): в случае
ошибки контакт надо восстановить контакт в контакт-листе.
Примеры можно приводить долго и много, теперь о том, что же такое Deferred.
Deferred – это сердце framework’а асинхронного сетевого программирования
Twisted в Python. Это простая и стройная концепция, которая
позволяет перевести синхронное программирование в асинхронный код, не изобретая
велосипед для каждой ситуации и обеспечивая высокое качества кода.
Deferred – это просто возвращаемый результат функции, когда этот результат
неизвестен (не был получен, будет получен в другой нити и т.п.) Что мы можем
сделать с Deferred? Мы можем «подвеситься» в цепочку обработчиков, которые
будут вызваны, когда результат будет получен. При этом Deferred может нести не
только положительный результат выполнения, но и исключения, сгенерированные
функцией или обработчиками, есть возможность исключения обработать, перевыкинуть
и т.д. Фактически, для синхронного кода есть более-менее однозначная параллель в
терминах Deferred. Для эффективной разработки с Deferred оказываются полезными
такие возможности языка программирования, как замыкания, лямбда-функци.
Continue reading…