Python 3.4, asyncio

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

Говорить о asyncio в чистом виде не очень интересно, т.к. в отличие от того же Twisted, это низкоуровневое API позволяющее работать с потоками ввода/вывода и блокировками. К счастью, прогресс не стоит на месте и уже имеется как минимум одна интересная библиотека для неблокирующей работы с протоколом HTTP – aiohttp, доступная через pip.

import asyncio
import aiohttp

@asyncio.coroutine                                                #(1)
def load(url, sem):
    with (yield from sem):                                        #(2)
        response = yield from aiohttp.request('GET', url)         #(3)
        return (yield from response.read_and_close(decode=True))  #(4)


@asyncio.coroutine                                                #(5)
def get_page(page, sem):
    url = "http://example.com/{}".format(page)
    text = yield from load(url, sem)                              #(6)
    print("URL: {}, TEXT: {}".format(url ,text[:20]))


def main():
    loop = asyncio.get_event_loop()                               #(7)
    sem = asyncio.Semaphore(10)                                   #(8)
    requests = [get_page("page_{}".format(x), sem) for x in range(1000)]
    f = asyncio.wait(requests)                                    #(9)
    loop.run_until_complete(f)                                    #(10)


if __name__ == "__main__":
    main()

Работа с asyncio особо не отличается от работы с библиотеками асинхронного IO на любом другом языке программирования за исключением встроенной поддержки coroutine. Конечно, возможности предоставляемы coroutine можно и не использовать, но на мой взгляд они сильно облегчают написание и поддержку не сложных асинхронных приложений, хотя и не подходят, с архитектурной точки зрения, для чего-то большого, требующего полного контроля над операциями ввода/вывода.

Ядром любой асинхронной IO библиотеки является обработчик сообщений, и asyncio тут не исключение. Так метод asyncio.get_event_loop() возвращает 7 объект реализующий интерфейс AbstractEventLoopPolicy. Само собой, на каждой из поддерживаемых платформ реализация обработчика сообщений будет разнится, где-то это будет kqueue(), где-то epoll() и т.д.

Библиотека asyncio поддерживает большое количество различных способов обработки событий на все случаи жизни. А в моем примере я просто ограничился функцией wait() 9, создающей объект Future по средствам которого можно дождаться завершения цепочки асинхронных операций 10.

Принцип работы функций являющихся coroutine 1, 5 хорошо проиллюстрирован в документации к библиотеке asyncio и в сочетании с yield from формой вызова дает отличные возможности для написания асинхронного приложения. Как видно из примера 346, результирующий код выглядит “синхронным” и не требует дополнительных обработчиков функций обратного вызова, что характерно для классических асинхронных приложений.

В качестве небольшого дополнения к примеру был добавлен семафор 8 для ограничения числа параллельных IO операций. Без подобного ограничения 2 вся тысяча запросов была бы отправлена одновременно, а это мало того что совершенно бессмысленно с точки зрения обработки данных, так еще и создаст лишнюю нагрузку на сетевую подсистему ОС.

Уверен, что количество библиотек содержащих реализации новых сетевых протоколов поверх asyncio в скором времени будет стремительно расти. Хотя я, в первую очередь, надеюсь что это событие послужит толчком, который ускорит портирование Twisted на Python3.

5 Comments Python 3.4, asyncio

  1. andreych

    Если верить документации, то они поддерживают файловый асинхрнонный IO тоже

    http://docs.python.org/dev/library/asyncio-eventloop.html#watch-file-descriptors.

    Я не множко посмотрел внутри, там все тот же kqueue и epoll, который всегда будет говорить, что файл готов для чтения и записи.

    Я что то не уловил?

    PS использую ruby, не python, смотрел из интереса.

    Reply
    1. Alexander Stavonin

      Да, относительно kqueue и epoll все верно. Но в той же Windows может использоваться IOCP, что уже даст неблокирующий асинхронный IO, а для *NIX есть AOI. Судя по документации к asyncio, на данный момент они реализовали поддержку только IOCP, т.е. на *NIX используется блокирующий асинхронный IO.

      Reply
      1. andreych

        По мне так это все-таки блокирующий синхронный IO (для файлов).
        А где про IOCP написано?

        Reply
      2. Andrey

        Все-таки неблокирующий I/O, так как без него, нет никакого смысла, ввиду того, что event loop работает в одном потоке с курутинами и если в курутине заблокировать I/O каким-нить блокирующим вызовом, например: чтение из сокета, который небыл переведен в non-bloking mode, то заблокируется весь тред, вместе с event loop. Для этого даже sleep() сделали в виде курутины (yield from async.sleep()), а не встроенный блокирующий time.sleep().

        P.S. Python, как язык, не имеет никаких ограничений на использование потоков и процессов, более того, потоки не green thread, а реально системные треды, которые скедулятся осью. Проблема в GIL, который в имплементации языка Python — интерпретаторе CPython, если без нее, то PyPy…

        Reply

Leave a Reply to Alexander Stavonin Cancel reply