<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Andrey Smirnov's Blog (callback)</title><link>http://www.smira.ru/</link><description></description><language>en</language><lastBuildDate>Sun, 11 Jan 2015 19:24:27 GMT</lastBuildDate><generator>http://getnikola.com/</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Асинхронное программирование: концепция Deferred</title><link>http://www.smira.ru/en/posts/20090210deferred-async-programming.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;&lt;/p&gt;&lt;p&gt;Асинхронная концепция программирования заключается в том, что результат
выполнения функции доступен не сразу же, а через некоторое время в виде&lt;/p&gt;
&lt;p&gt;некоторого асинхронного (нарушающего обычный порядок выполнения) вызова. Зачем&lt;/p&gt;
&lt;p&gt;такое может быть полезно?  Рассмотрим несколько примеров.&lt;/p&gt;
&lt;p&gt;Первый пример - сетевой сервер, веб-приложение. Чаще всего как таковых&lt;/p&gt;
&lt;p&gt;вычислений на процессоре такие приложения не выполняют. Большая часть времени&lt;/p&gt;
&lt;p&gt;(реального, не процессорного) тратится на ввод-вывод: чтение запроса от&lt;/p&gt;
&lt;p&gt;клиента, обращение к диску за данными, сетевые обращение к другим подсистемам&lt;/p&gt;
&lt;p&gt;(БД, кэширующие сервера, RPC и т.п.), запись ответа клиенту. Во время этих&lt;/p&gt;
&lt;p&gt;операций ввода-вывода процессор простаивает, его можно загрузить обработкой&lt;/p&gt;
&lt;p&gt;запросов других клиентов.  Возможны различные способы решить эту задачу: отдельный&lt;/p&gt;
&lt;p&gt;процесс на каждое соединение (&lt;a href="http://httpd.apache.org/"&gt;Apache&lt;/a&gt; mpm_prefork, &lt;a href="http://www.postgresql.org"&gt;PostgreSQL&lt;/a&gt;, &lt;a href="http://www.php.net"&gt;PHP&lt;/a&gt; FastCGI),&lt;/p&gt;
&lt;p&gt;отдельный поток (нить) на каждое соединение или комбинированный вариант&lt;/p&gt;
&lt;p&gt;процесс/нить (&lt;a href="http://httpd.apache.org/"&gt;Apache&lt;/a&gt; mpm_worker, &lt;a href="http://mysql.org"&gt;MySQL&lt;/a&gt;). Подход с использованием процессов или&lt;/p&gt;
&lt;p&gt;нитей перекладывает мультиплексирование процессора между обрабатываемыми&lt;/p&gt;
&lt;p&gt;соединениями на ОС, при этом расходуется относительно много ресурсов (память,&lt;/p&gt;
&lt;p&gt;переключения контекста и т.п.), такой вариант не подходит для обработки&lt;/p&gt;
&lt;p&gt;большого количества одновременных соединений, но идеален для ситуации, когда&lt;/p&gt;
&lt;p&gt;объем вычислений достаточно высок (например, в СУБД). К плюсам модели нитей и&lt;/p&gt;
&lt;p&gt;процессов можно добавить потенциальное использование всех доступных процессоров&lt;/p&gt;
&lt;p&gt;в многопроцессорной архитектуре.&lt;/p&gt;
&lt;p&gt;Альтернативой является использование однопоточной модели с использованием&lt;/p&gt;
&lt;p&gt;примитивов асинхронного ввода-вывода, предоставляемых ОС (select, poll, и&lt;/p&gt;
&lt;p&gt;т.п.). При этом объем ресурсов на каждое новое обслуживаемое соединение не&lt;/p&gt;
&lt;p&gt;такой большой (новый сокет, какие-то структуры в памяти приложения). Однако&lt;/p&gt;
&lt;p&gt;программирование существенно усложняется, т.к. данные из сетевых сокетов&lt;/p&gt;
&lt;p&gt;поступают некоторыми "отрывками", причем за один цикл обработки данные поступают от разных&lt;/p&gt;
&lt;p&gt;соединений, находящихся в разных состояниях, часть соединений могут быть&lt;/p&gt;
&lt;p&gt;входящими от клиентов, часть - исходящими к внешним ресурсам (БД, другой сервер&lt;/p&gt;
&lt;p&gt;и т.п.). Для упрощения разработки используются различные концепции: callback,&lt;/p&gt;
&lt;p&gt;конечные автоматы и другие. Примеры сетевых серверов, использующих асинхронный&lt;/p&gt;
&lt;p&gt;ввод-вывод: &lt;a href="http://sysoev.ru/nginx/"&gt;nginx&lt;/a&gt;, &lt;a href="http://lighttpd.net/"&gt;lighttpd&lt;/a&gt;, &lt;/p&gt;
&lt;p&gt;&lt;a href="http://haproxy.1wt.eu/"&gt;HAProxy&lt;/a&gt;, &lt;a href="https://developer.skype.com/SkypeGarage/DbProjects/PgBouncer"&gt;pgBouncer&lt;/a&gt;, и т.д. Именно при такой&lt;/p&gt;
&lt;p&gt;однопоточной модели возникает необходимость в асинхронном программировании.&lt;/p&gt;
&lt;p&gt;Например, мы хотим выполнить запрос в БД. С точки зрения программы выполнение&lt;/p&gt;
&lt;p&gt;запроса - это сетевой ввод-вывод: соединение с сервером, отправка запроса,&lt;/p&gt;
&lt;p&gt;ожидание ответа, чтение ответа сервера БД. Поэтому если мы вызываем&lt;/p&gt;
&lt;p&gt;функцию "выполнить запрос БД", то она сразу вернуть результат не сможет&lt;/p&gt;
&lt;p&gt;(иначе она должна была бы заблокироваться), а вернет лишь нечто, что позволит&lt;/p&gt;
&lt;p&gt;впоследствие получить результат запроса или, возможно, ошибку (нет соединения&lt;/p&gt;
&lt;p&gt;с сервером, некорректный запрос и т.п.) Этим возвращаемым значением удобно&lt;/p&gt;
&lt;p&gt;сделать именно Deferred.&lt;/p&gt;
&lt;p&gt;Второй пример связан с разработкой обычных десктопных приложений. Предположим,&lt;/p&gt;
&lt;p&gt;мы решили сделать аналог &lt;a href="http://www.miranda-im.org/"&gt;Miranda&lt;/a&gt; (&lt;a href="http://qip.ru/"&gt;QIP&lt;/a&gt;, &lt;a href="http://mdc.ru/"&gt;MDC&lt;/a&gt;, ...), то есть свой мессенджер.&lt;/p&gt;
&lt;p&gt;В интерфейсе программы есть контакт-лист, где можно удалить контакт. Когда&lt;/p&gt;
&lt;p&gt;пользователь выбирает это действие, он ожидает что контакт исчезнет на экране &lt;/p&gt;
&lt;p&gt;и что он действительно удалится из контакт-листа. На самом деле операция&lt;/p&gt;
&lt;p&gt;удаления из серверного контакт-листа опирается на сетевое взаимодействие&lt;/p&gt;
&lt;p&gt;с сервером, при этом пользовательский интерфейс не должен быть заблокирован&lt;/p&gt;
&lt;p&gt;на время выполнения этой операции, поэтому в любом случае после выполнения&lt;/p&gt;
&lt;p&gt;операции потребуется некоторое асинхронное взаимодействие с результатом операции.&lt;/p&gt;
&lt;p&gt;Можно использовать механизм сигналов-слотов, callback'ов или что-то еще,&lt;/p&gt;
&lt;p&gt;но лучше всего подойдет Deferred: операция удаления из контакт-листа возвращает&lt;/p&gt;
&lt;p&gt;Deferred, в котором обратно придет либо положительный результат (всё хорошо),&lt;/p&gt;
&lt;p&gt;либо исключение (точная ошибка, которую надо сообщить пользователю): в случае&lt;/p&gt;
&lt;p&gt;ошибки контакт надо восстановить контакт в контакт-листе.&lt;/p&gt;
&lt;p&gt;Примеры можно приводить долго и много, теперь о том, что же такое Deferred.&lt;/p&gt;
&lt;p&gt;Deferred - это сердце framework'а асинхронного сетевого программирования &lt;/p&gt;
&lt;p&gt;&lt;a href="http://twistedmatrix.com/"&gt;Twisted&lt;/a&gt; в Python. Это простая и стройная концепция, которая&lt;/p&gt;
&lt;p&gt;позволяет перевести синхронное программирование в асинхронный код, не изобретая&lt;/p&gt;
&lt;p&gt;велосипед для каждой ситуации и обеспечивая высокое качества кода.&lt;/p&gt;
&lt;p&gt;Deferred - это просто возвращаемый результат функции, когда этот результат&lt;/p&gt;
&lt;p&gt;неизвестен (не был получен, будет получен в другой нити и т.п.) Что мы можем&lt;/p&gt;
&lt;p&gt;сделать с Deferred? Мы можем "подвеситься" в цепочку обработчиков, которые&lt;/p&gt;
&lt;p&gt;будут вызваны, когда результат будет получен. При этом Deferred может нести не &lt;/p&gt;
&lt;p&gt;только положительный результат выполнения, но и исключения, сгенерированные&lt;/p&gt;
&lt;p&gt;функцией или обработчиками, есть возможность исключения обработать, перевыкинуть&lt;/p&gt;
&lt;p&gt;и т.д. Фактически, для синхронного кода есть более-менее однозначная параллель в&lt;/p&gt;
&lt;p&gt;терминах Deferred. Для эффективной разработки с Deferred оказываются полезными&lt;/p&gt;
&lt;p&gt;такие возможности языка программирования, как замыкания, лямбда-функци.&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;Приведем пример синхронного кода и его альтернативу в терминах Deferred:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;pre lang="python"&gt;
&lt;p&gt;try:
    # Скачать по HTTP некоторую страницу
    page = downloadPage(url)
    # Распечатать содержимое
    print page
except HTTPError, e:
    # Произошла ошибка
    print "An error occured: %s", e
&lt;/p&gt;&lt;/pre&gt;
&lt;p&gt;В асинхронном варианте с Deferred он был бы записан следующим образом:&lt;/p&gt;
&lt;pre lang="python"&gt;

def printContents(contents):
    """
    Callback, при успешном получении текста страницы,
    распечатываем её содержимое.
    """
    print contents


def handleError(failure):
    """
    Errback (обработчик ошибок), просто распечатываем текст ошибки.
    """

    # Мы готовы обработать только HTTPError, остальные исключения
    # "проваливаются" ниже.
    failure.trap(HTTPError)
    # Распечатываем само исключение
    print "An error occured: %s", failure


# Теперь функция выполняется асинхронно и вместо непосредственного

# результата мы получаем Deferred

deferred = downloadPage(url)

# Навешиваем на Deferred-объект обработчики успешных результатов

# и ошибок (callback, errback).

deferred.addCallback(printContents)

deferred.addErrback(handleError)

&lt;/pre&gt;

&lt;p&gt;На практике обычно мы возвращаем Deferred из функций, которые получают&lt;/p&gt;
&lt;p&gt;Deferred в процессе своей работы, навешиваем большое количество обработчиков,&lt;/p&gt;
&lt;p&gt;обрабатываем исключения, некоторые исключения возвращаем через Deferred (выбрасываем&lt;/p&gt;
&lt;p&gt;наверх). В качестве более сложного примера приведем код в асинхронном&lt;/p&gt;
&lt;p&gt;варианте для примера атомарного счетчика из статьи про &lt;a href="http://www.smira.ru/2009/01/21/data-structures-in-memcached-memcachedb/#atomic-counter"&gt;структуры данных в memcached&lt;/a&gt;,&lt;/p&gt;
&lt;p&gt;здесь мы предполагаем, что доступ к memcached как сетевому сервису идет через Deferred, т.е.&lt;/p&gt;
&lt;p&gt;методы класса Memcache возвращают Deferred (который вернет либо результат операции, либо ошибку):&lt;/p&gt;
&lt;pre lang="python"&gt;

class MCCounter(MemcacheObject):
    def __init__(self, mc, name):
        """
        Конструктор.

        @param name: имя счетчика
        @type name: C{str}
        """
        super(MCCounter, self).__init__(mc)
        self.key = 'counter' + name

    def increment(self, value=1):
        """
        Увеличить значение счетчика на указанное значение.

        @param value: инкремент
        @type value: C{int}
        @return: Deferred, результат операции
        """
        def tryAdd(failure):
            # Обрабатываем только KeyError, всё остальное "вывалится"
            # ниже
            failure.trap(KeyError)

            # Пытаемся создать ключ, если раз его еще нет
            d = self.mc.add(self.key, value, 0)
            # Если вдруг кто-то еще создаст ключ раньше нас,
            # мы это обработаем
            d.addErrback(tryIncr)
            # Возвращаем Deferred, он "вклеивается" в цепочку
            # Deferred, в контексте которого мы выполняемся
            return d

        def tryIncr(failure):
            # Всё аналогично функции tryAdd
            failure.trap(KeyError)

            d = self.mc.incr(self.key, value)
            d.addErrback(tryAdd)
            return d

        # Пытаемся выполнить инкремент, получаем Deferred
        d = self.mc.incr(self.key, value)
        # Обрабатываем ошибку
        d.addErrback(tryAdd)
        # Возвращаем Deferred вызывающему коду, он может тем самым:
        #  а) узнать, когда операция действительно завершится
        #  б) обработать необработанные нами ошибки (например, разрыв соединения)
        return d

    def value(self):
        """
        Получить значение счетчика.

        @return: текущее значение счетчика
        @rtype: C{int}
        @return: Deferred, значение счетчика
        """
        def handleKeyError(failure):
            # Обрабатываем только KeyError
            failure.trap(KeyError)

            # Ключа нет - возвращаем 0, он станет результатом
            # вышележащего Deferred
            return 0

        # Пытаемся получить значение ключа
        d = self.mc.get(self.key)
        # Будем обрабатывать ошибку отсутствия ключа
        d.addErrback(handleKeyError)
        # Возвращаем Deferred, наверх там можно будет повеситься
        # на его callback и получить искомое значение счетчика
        return d
&lt;/pre&gt;

&lt;p&gt;На практике приведенный выше код можно написать "короче", объединяя часто используемые операции, например:&lt;/p&gt;
&lt;pre lang="python"&gt;
    return self.mc.get(self.key).addErrback(handleKeyError)
&lt;/pre&gt;

&lt;p&gt;Практически для каждой конструкции синхронного кода можно найти аналог в асинхронной концепции с Deferred:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;последовательности синхронных операторов соответствует цепочка callback с асинхронными вызовами;&lt;/li&gt;
&lt;li&gt;вызову одной подпрграммы с вводом-выводом из другой соответствует возврат Deferred из Deferred (ветвление Deferred);&lt;/li&gt;
&lt;li&gt;глубокой цепочки вложенности, распространению исключений по стеку соответствует цепочка функций, возвращающие друг другу
   Deferred;&lt;/li&gt;
&lt;li&gt;блокам try..except соответствуют обработчики ошибок (errback), которые могут "пробрасывать" исключения дальше,
   любое исключение в callback переводит выполнение в errback;&lt;/li&gt;
&lt;li&gt;для "параллельного" выполнения асинхронных операций есть &lt;code&gt;DeferredList&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Нити часто применяются в асинхронных программах для осуществления вычислительных процедур, осуществления блокирующегося &lt;/p&gt;
&lt;p&gt;ввода-вывода (когда не существует асинхронного аналога). Всё это легко моделируется в простой модели 'worker', тогда &lt;/p&gt;
&lt;p&gt;нет необходимости при грамотной архитектуре в явной синхронизации, при этом всё элегантно включается в общий поток&lt;/p&gt;
&lt;p&gt;вычислений с помощью Deferred:&lt;/p&gt;
&lt;pre lang="python"&gt;

def doCalculation(a, b):
    """
    В этой функции осуществляются вычисления, синхронные операции ввода-вывода,
    не затрагивающие основной поток.
    """

    return a/b


def printResult(result):
    print result


def handleDivisionByZero(failure):
    failure.trap(ZeroDivisionError)

    print "Ooops! Division by zero!"


deferToThread(doCalculation, 3, 2).addCallback(printResult).addCallback(
    lambda _: deferToThread(doCalculation, 3, 0).addErrback(handleDivisionByZero))
&lt;/pre&gt;

&lt;p&gt;В приведенном выше примере функция &lt;code&gt;deferToThread&lt;/code&gt; переносит выполнение указанной функции в отдельную нить и возвращает&lt;/p&gt;
&lt;p&gt;Deferred, через который будет асинхронно получен результат выполнения функции или исключение, если они будет выброшено.&lt;/p&gt;
&lt;p&gt;Первое деление (3/2) выполняется в отдельной нити, затем распечатывается его результат на экран, а затем запускается&lt;/p&gt;
&lt;p&gt;еще одно вычисление (3/0), которое генерирует исключение, обрабатываемое функцией &lt;code&gt;handleDivisionByZero&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;В одной статье не описать и части того, что хотелось бы сказать о Deferred, мне удалось не написать ни слова о том,&lt;/p&gt;
&lt;p&gt;как же они работают. Если успел заинтересовать - читайте материалы ниже, а я обещаю написать еще.&lt;/p&gt;
&lt;h3&gt;Дополнительные материалы&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Документация Twisted Framework:&lt;/li&gt;
&lt;li&gt;&lt;a href="http://twistedmatrix.com/projects/core/documentation/howto/async.html"&gt;Основы асинхронного программирования&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://twistedmatrix.com/projects/core/documentation/howto/defer.html"&gt;Использование Deferred&lt;/a&gt;, &lt;a href="http://twistedmatrix.com/projects/core/documentation/howto/gendefer.html"&gt;Откуда берутся Deferred?&lt;/a&gt;, &lt;a href="http://twistedmatrix.com/projects/core/documentation/howto/deferredindepth.html"&gt;Детальное описание Deferred&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Deferred в других языках программирования:&lt;/li&gt;
&lt;li&gt;JavaScript: &lt;a href="http://javascript.ru/tutorial/async/deferred-deep"&gt;Использование Deferred в JavaScript&lt;/a&gt;, &lt;a href="http://code.netstream.ru/wiki/QooxdooTwistedDeferred"&gt;Deferred в qooxdoo&lt;/a&gt;, &lt;a href="http://ajaxian.com/archives/dojos-deferred-api"&gt;Deferred в Dojo&lt;/a&gt;, &lt;a href="http://mochikit.com/doc/html/MochiKit/Async.html#fn-deferred"&gt;Deferred в MochiKit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;C++: &lt;a href="http://twistedmatrix.com/pipermail/twisted-python/2009-January/019119.html"&gt;1&lt;/a&gt;, &lt;a href="http://www.twistedmatrix.com/pipermail/twisted-python/2008-October/018548.html"&gt;2&lt;/a&gt;, &lt;a href="http://web.me.com/namezys/main/%D0%91%D0%BB%D0%BE%D0%B3/%D0%97%D0%B0%D0%BF%D0%B8%D1%81%D0%B8/2009/1/26_%D0%AF_%D0%B4%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BB_sequence._%D0%A2%D0%BE_%D0%B5%D1%81%D1%82%D1%8C_%D0%BF%D0%B8%D1%82%D0%BE%D0%BD%D0%BE%D0%B2%D1%81%D0%BA%D0%B8%D0%B5_%D0%B4%D0%B5%D1%84%D0%B5%D1%80%D1%8B.html"&gt;3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;&lt;a href="http://burus.org/2008/12/16/twisted-classic-examples/"&gt;Александр Бурцев о Twisted&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</description><guid>http://www.smira.ru/en/posts/20090210deferred-async-programming.html</guid><pubDate>Tue, 10 Feb 2009 06:49:28 GMT</pubDate></item></channel></rss>