Найти в Дзене

Асинхронность, многопоточность и многопроцессность.

Оглавление

Всем привет. Какой-то суетливый период, вышибло больше чем на месяц, хотя план был публиковать хотя бы по посту в неделю. Как-то неожиданно появилось сразу несколько путей дальнейшего развития и сейчас приходится разрываться с выбором. Чтобы хоть как-то вернуться в ритм, начинаю цикл переводов по асинхронному программированию с сайта realpython.

Может показаться, что процесс выполняется одновременно, но по факту подпрограммы получают доступ к ресурсу на какой-то промежуток времени)
Может показаться, что процесс выполняется одновременно, но по факту подпрограммы получают доступ к ресурсу на какой-то промежуток времени)

_________________________________________________________________________________________

Async IO это подход к одновременному программированию, который получил поддержку начиная с Python 3.4 → Python 3.7 и скорее всего будет поддерживаться дальше

Возможно, в этот момент вы с ужасом подумали «Одновременность, параллельность, многопоточность, многопроцессность. Так много нужно узнать, для решения каких задач нужна асинхронность?»

Эта статья поможет вам ответить на этот вопрос и дать представление о подходе к асинхронности, который используется в Python

О чем будет в этой заметке:

- Асинхронность (Asynchronous IO (async IO): это парадигма(модель) которая имеет различные реализации во многих языках программирования

- async/awai: два новых ключевых слова в Python, которые используются, чтобы взаимодействовать с корутинами (coroutines)

- asyncio: Python библиотека которая обладает основными инструментами и API, чтобы запускать и управлять корутинами

Корутина (специализированная функция-генератор) — сердце асинхронности (async IO) в Python и мы обязательно хорошенько с ними разберемся. Но позже.

ЗАМЕТКА: в этой статье async IO используется при обозначении общей концепции, а asyncio для библиотеки в Python.

Прим. Переводчика(всегда хотел это написать :) ): в статье в дальнейшем при упоминании библиотеки будет указано «библиотека»

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

Настройте окружение

Вам потребуется Python 3.7 или выше, чтобы полностью воспользоваться материалами этой статьи. Так же вам потребуются библиотеки aiohttp и aiofiles:

$ python3.7 -m venv ./py37async
$ source ./py37async/bin/activate # Windows: .\py37async\Scripts\activate.bat
$ pip install --upgrade pip aiohttp aiofiles # Optional: aiodns

Итак, начинаем.

Верхнеуровневый взгляд на Async IO

Асинхронность чуть менее известна чем её «большие братья» многопоточность и многопроцессность. Эта часть статьи должна дать вам представление, что же такое асинхронность и для каких задач её обычно используют.

Где применяется асинхронность?

Одновременность и параллелизм достаточно обширные понятия, которые не так просто объяснить и постигнуть. Несмотря на то, что эта статья фокусируется на асинхронности и её понимании в Python, стоит уделить минуту, чтобы разобрать само понятие асинхронности на части и объяснить в каком контексте они собираются в большой, иногда сбивающий с толку пазл.

Параллельность(Parallelism) - выполнение множества операций в одно и то же время. Многопроцессность (Multiprocessing) чаще всего относится к эффекту параллельности и означает распределение задач между частями центральной вычислительной системы (процессорами или ядрами). Многопроцессность хорошо себя показали при задачах связанных с циклами for и математическими вычислениями.

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

Многопоточность это модель, при которой задача разбивается на множество потоков, занимающихся её решением. Один процесс может содержать множество потоков. Python достаточно специфично относится к многопоточности спасибо его GIL (Global Interpreter Lock), но это уже совсем другая история, которая лежит вне нашей текущей статьи.

Самое важное, что стоит знать о многопоточности — она прекрасно решает задачи связанные с вводом/выводом (I/O) информации. Если задачи, привязанные к расчетам задействуют процессоры на протяжении всего периода решения, то задачи ввода/вывода допускают большие периоды ожиданий.

Чтобы немного подытожить, одновременность включает в себя и многопроцессность (идеальна для задач, требующих многих расчетов) и многопоточность (подходит для задач ввода/вывода). Многопроцессность это форма параллельности, которая является особым типом одновременности. Стандартные библиотеки Python достаточно давно могут поддерживать оба формата с помощью пакетов multiprocessing, threading и concurrent.futures.

Настало время добавить в команду нового игрока. В течение последних нескольких лет в Cpython была встроена новая конструкция: асинхронное программирование стало доступно через библиотеку asyncio и новые командные слова async и await. Чтобы быть честными, как концепт асинхронность уже имела поддержку в ряде других языков и окружений, таких как Go, C# и Scala.

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

Асинхронный код построен вокруг одного потока и одного процесса: он использует кооперативную многозадачность. Термин, который вы выучите к концу этой статьи. Другими словами, асинхронность позволяет создать впечатление многопоточности, несмотря на только 1 поток и процесс. Корутины (главная особенность асинхронности) могут находиться в графике одновременно, но не исполняться.

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

Это заставляет нас задуматься, что значит быть асинхронным? Для наших целей наиболее важны два свойства:

- асинхронные подпрограммы способны вставать на паузу, ожидая получение какого-то результата и давая работать другим подпрограммам

- учитывая сказанное выше, асинхронный код позволяет выполнять задачи одновременно. Если быть точным, то он имитирует ощущение одновременности.

Чтобы визуально представить различия в терминах и закрепить их, вот диаграмма:

-2

На этом предлагаю остановиться объяснять разницу между моделями программирования, использующими одновременность. Эта статья будет посвящена только одной из частей — асинхронности. Тому как её использовать, и какие API построены вокруг неё. Для дальнейшего изучения вопроса мультипроцессинг vs многопоточность vs асинхронность можете почитать заметку Джима Андерсона overview of concurrency in Python. Он отлично разобрал этот вопрос.

Объяснение Async IO

На первый взгляд асинхронность может показаться контринтуитивной и парадоксальной. Как может получиться одновременно решать несколько задач, используя только одно ядро процессора и один поток? У меня никогда не получалось хорошо подбирать примеры, поэтому я перефразирую сказанное Мигелем Гринбергом на PyCon 2017, который прекрасно подходит:

Гроссмейстер Юдит Полгар посетила выставку шахмат, на которой она играла с множеством игроков-любителей. У неё было 2 варианта проводить партии: синхронно и асинхронно
Условия:
24 оппонента
Юдит делает каждый ход за 5 секунд
оппоненту дается 55 секунд на ход
игры обычно длятся 30 пар ходов (60 ходов в общем)
Синхронная версия событий: Юдит играет одну игру до завершения за раз. Игра занимает (5+55)*30 == 1800 секунд или 30 минут. Всё мероприятие займет 24*30 == 720 минут или 12 часов.
Асинхронная версия: Юдит перемещается от стола к столу и делает 1 ход на каждом. Пока она делает другие ходы, оппоненты размышляют над своим. 1 ход в каждой игре займет 24*5 == 120 секунд или 2 минуты. Всё мероприятие уместится в 120*30 == 3600 секунд или один час

Есть только одна Юдит Полгар, у которой есть только 2 руки и возможность сделать 1 ход за раз. Но возможность играть асинхронно уменьшает время общей игры с 12 до 1 часа. Итак, кооперативную многозадачность можно объяснить через один цикл, который взаимодействует со множеством заданий, давая им оптимальное время на работу.

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

Async IO это не просто

Как-то раз, я слышал как сказали: «Используй асинхронность, когда можешь, а многопоточность, когда должен». Истина в том, что многопоточность существенно увеличивает риски схватить ошибку. Асинхронность избегает части проблем, с которой вы могли бы столкнуться при использовании нескольких потоков.

Но было бы нечестным сказать, что асинхронность в Python это просто. Учтите, как только вы копнете немного поглубже, можете столкнуться с трудностями. Асинхронный код в Python крутится вокруг таких концептов как обратные вызовы, эвенты, транспорты, протоколы. Даже терминология уже выглядит пугающей. Так же мешают постоянные изменения в API.

На нашу удачу библиотека asyncio «выросла» до того момента, что по ней стало появляться больше качественной информации, большая часть её методов перестала быть предварительными, а документация уже прошла глобальную переработку.

__________________________________________________________________________________________

Спасибо тем, кто дочитал. Первая часть получилась мега-теоретической. Начиная со второй увидим примеры с кодом. Постараюсь не теряться и вернуться с продолжением в ближайшее время)

Наука
7 млн интересуются