для чего может использоваться асинхронность

Асинхронное программирование. Часть 1: Как работает процессор

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

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

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

Язык программирования C# предоставляет разработчикам множество инструментов для создания приложений, которые могут работать с большим количеством потоков, выполняя их параллельно и асинхронно. Это одна из причин популярности этого языка, а также главная причина, почему именно он выбран для этой серии статей.

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

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

Информация в следующих разделах сильно упрощена.

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

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

Как работает процессор

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

5 * 5 = 5 + 5 + 5 + 5 + 5

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

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

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

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

Количество тиков измеряется в герцах (Гц) — это единица измерения частоты протекания периодических процессов. Например, если мимо вашего дома раз в секунду проезжает гоночный болид, то его частота будет равна 1 Гц. Если болид проезжает два раза в секунду, то его частота — 2 Гц; если он проезжает трижды, то давно пора подумать о переезде.

Процессор так быстро выполняет процессы, что его частота измеряется в гигагерцах.

1 ГГц = 1 000 000 000 Гц

Частота современных процессоров обычно равна 2-3 ГГц.

Как процессор выполняет программы

Каждая программа состоит из множества процессов: нужно 500 раз провести сложение, записать данные в 2000 ячеек, перемножить всё это, а потом, наконец, поделить.

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

Тогда код программы будет выглядеть примерно так:

Пока выполняется этот цикл, вы не сможете сделать ничего, что не прописано внутри него. И, как видите, здесь нет строчек вроде проигрывать музыку или листать ленту. Более того, даже строчек для отслеживания движений мышью нет.

Поэтому, чтобы компьютерами было удобно пользоваться, мы делим все выполняемые программы на потоки. Допустим, у нас запущено 10 программ, а процессор работает на скорости 100 тиков в секунду.

Тогда каждый поток получит по 10 тиков. То есть процессор будет в течение 10 тиков выполнять инструкции от одного потока, а потом переходить к инструкциям другого — и так по кругу. Также у каждого потока есть приоритет: более важные программы будут получать больше тиков.

В вашей операционной системе каждая запущенная программа выполняется в отдельном потоке. Это происходит автоматически, потому что так спроектированы ОС. Но когда вы пишете программу, вы также можете создавать новые потоки: это позволит вам делать приложения удобнее для пользователей.

Когда нужна асинхронность

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

Если же всё это выполняется в одном потоке, то приложение будет подвисать, когда выполняется сложная инструкция. В ОС Windows часто можно заметить, что, когда приложение что-то делает, а вы кликаете на него, то в заголовке окна можно увидеть словосочетание «Не отвечает».

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

Пример асинхронного приложения

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

В следующих статьях вы узнаете, как всё устроено, а пока посмотрим, как это работает:

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

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

Заключение

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

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

Многое об АМП вы можете узнать на нашем курсе «Профессия C#-разработчик». Там вы освоите все инструменты, необходимые программисту на C#, чтобы постепенно стать профессионалом.

Источник

Асинхронность в JavaScript: Пособие для тех, кто хочет разобраться

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

На JavaScript легко писать. Достаточно взять пару библиотек или модный фреймворк, прочитать несложный туториал и все — через пару часов у вас простой работающий интерфейс.

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

В JavaScript нет многопоточности. Несмотря на то, что мы уже можем полноценно использовать вебворкеры, из них нельзя менять DOM или вызывать методы объекта window. Одним словом, не многопоточность, а сплошное разочарование.

Причины таких ограничений понятны. Представьте себе, что два параллельных потока пытаются наперегонки поменять один и тот же узел в DOM с непредсказуемым результатом. Представили? Мне тоже стало не по себе.

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

С DOM-деревом работают в одном потоке, чтобы гарантировать целостность и непротиворечивость данных, но как программировать интерфейс с одним потоком? Ведь сама суть интерфейса — в асинхронности. Именно для этого придуманы асинхронные функции. Они выполняются не сразу, а после наступления события. Интересно, что эти функции — не часть JavaScript-движков. Вызов setTimeout на чистом V8 приводит к ошибке, так как в V8 нет такой функции. Тогда откуда же появляется setTimeout или requestAnimationFrame или addEventListener?

Асинхронность внутри

Движок JavaScript похож на мясорубку, бесконечно перемалывающую операции, которые последовательно берутся из стека вызовов (1). Код выполняется линейно и последовательно. Удалить операцию из стека нельзя, можно только прервать поток выполнения. Поток выполнения прерывается, если вызвать что-то типа alert или «исключение».

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

Каждая операция содержит контекст — некую область памяти, из которой доступны данные. Контексты расположены в памяти в виде дерева. Каждому листу в дереве доступны области видимости, которые определены в родительских ветках и в корне (глобальной области видимости). Функции в JavaScript — это данные, они хранятся в памяти именно как данные и поэтому передаются как переменные или возвращаются из других функций.

Асинхронные операции выполняются не в движке, а в окружении (5,6). (Как подсказал forgotten это не совсем так: мы можем из стека вызовов сразу же положить функцию в очередь вызовов и таким образом чистый движок тоже будет работать асинхронно)
Окружение — надстройка на движком. NodeJS и Chrome для движка V8 и Firefox для Gecko. Иногда окружение еще называют web API.
Чтобы создать асинхронный вызов, в web API передается ссылка на функцию, которая выполнится позже или не выполнится вовсе.

У функции есть свой контекст или своя область памяти (3), в которой она определена. Функция имеет доступ к этой области памяти и ко всем родителям этой области памяти. Такие функции называются замыканиями. С этой точки зрения, все функции в JavaScript — замыкания, так как все они имеют контекст.

Web API и JavaScrtipt движок работают независимо. Web API решает, в какой момент функция двигается дальше, в очередь вызовов (2).

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

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

Это очень важно! Разработчику не нужно самому контролировать параллельный доступ к ресурсам, асинхронную работу за него выполняет окружение. Окружения определяют различия между браузером и node.js, ведь на node.js мы пишем сетевые приложения или обращаемся напрямую к жесткому диску, а из Chrome перехватываем клики по кнопкам, используя один и тот же движок.

В очереди вызовов нельзя отменять отдельные операции. Это делается в окружении (removeEventListener — в качестве примера).

Примеры

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

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

Клик вызовет «тяжелую» для расчета функцию. После клика в консоль пишется start, в конце выполнения функции — end. Выполнение функции на моем ноутбуке занимает несколько секунд. Все время, пока выполняется функция, квадратик мигает. Это значит, что анимации в CSS выполняются асинхронно JavaScript-коду.

Но что будет, если вместо opacity менять размер?

Квадратик зависнет на время выполнения функции. Дело в том, что CSS-свойство height обращается к DOM. Как мы помним, к DOM можно обращаться только из одного потока, чтобы не было проблем с параллельным доступом.

Делаем вывод, что для анимации лучше пользоваться свойствами, которые не меняют DOM (transform, opacity и т.д.). А всю тяжелую работу в JavaScript лучше делать асинхронно. Например вот так.

Код написан для наглядности и на коленке, в бою применять не рекомендуется. Мы делим большой кусок работы на маленькие и выполняем асинхронно. При этом интерфейс не блокируется. Для таких расчетов можно пользоваться веб-воркерами.

Вывод

Благодаря JavaScript мы пишем асинхронные приложения, не задумываясь о многопоточности: о целостности и непротиворечивости данных. За эти преимущества мы платим огромным числом обратных вызовов, блокированием основного потока и постоянными потерями контекста.

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

Источник

Асинхронность в программировании

Авторизуйтесь

Асинхронность в программировании

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

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

При вызове методов read() и write() текущий поток исполнения будет прерван в ожидании ввода-вывода по сети. Причём большую часть времени программа будет просто ждать. В высоконагруженных системах чаще всего так и происходит — почти всё время программа чего-то ждёт: диска, СУБД, сети, UI, в общем, какого-то внешнего, независимого от самой программы события. В малонагруженных системах это можно решить созданием нового потока для каждого блокирующего действия. Пока один поток спит, другой работает.

Но что делать, когда пользователей очень много? Если создавать на каждого хотя бы один поток, то производительность такого сервера резко упадёт из-за того, что контекст исполнения потока постоянно сменяется. Также на каждый поток создаётся свой контекст исполнения, включая память для стека, которая имеет минимальный размер в 4 КБ. Эту проблему может решить асинхронное программирование.

Асинхронность

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

Callbacks

Для написания асинхронной программы можно использовать callback-функции (от англ. callback — обратный вызов) — функции, которые будут вызваны асинхронно каким-либо обработчиком событий после завершения задачи. Переписанный пример сервера на callback-функциях:

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

У такого подхода есть несколько проблем. Первую в шутку называют callback hell. Достаточно погуглить картинки на эту тему, чтобы понять, насколько это нечитаемо и некрасиво. В нашем примере всего две вложенные callback-функции, но их может быть намного больше.

Async/Await

Попробуем сделать асинхронный код так, чтобы он выглядел как синхронный. Для большего понимания немного поменяем задачу: теперь нам необходимо прочитать данные из СУБД и файла по ключу, переданному по сети, и отправить результат обратно по сети.

Пройдём по программе построчно:

Это быстрее, чем последовательное ожидание сначала БД, затем файла. Во многих реализациях производительность async / await лучше, чем у классических callback-функций, при этом такой код читается как синхронный.

Корутины

Описанный выше механизм называется сопрограммой. Часто можно услышать вариант «корутина» (от англ. coroutine — сопрограмма).

Далее будут описаны различные виды и способы организации сопрограмм.

Несколько точек входа

Для большего понимания рассмотрим код на языке Python:

Программа выведет всю последовательность чисел факториала с номерами от 0 до 41.

Stackful и Stackless

В зависимости от использования стека корутины делятся на stackful, где каждая из корутин имеет свой стек, и stackless, где все локальные переменные функции сохраняются в специальном объекте.

На рисунке ниже вызов async создаёт новый стек-фрейм и переключает исполнение потока на него. Это практически новый поток, только исполняться он будет асинхронно с основным.

yield в свою очередь возвращает обратно предыдущий стек-фрейм на исполнение, сохраняя ссылку на конец текущего в предыдущий стек.

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

Наличие собственного стека позволяет делать yield из вложенных вызовов функций, но такие вызовы сопровождаются полным созданием/сменой контекста исполнения программы, что медленней, чем stackless корутины.

Более производительными, но вместе с тем и более ограниченными, являются stackless корутины. Они не используют стек, и компилятор преобразует функцию, содержащую корутины, в конечный автомат без корутин. Например, код:

Будет преобразован в следующий псевдокод:

Симметричные и асимметричные

Корутины также делятся на симметричные и асимметричные.

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

Вывод

Асинхронное программирование является очень мощным инструментом для оптимизации высоконагруженных программ с частым ожиданием системы. Но, как и любую сложную технологию, её нельзя использовать только потому, что она есть. Необходимо всегда задавать себе вопрос: а нужна ли мне эта технология? Какую практическую пользу она мне даст? Иначе разработчики рискуют потратить очень много сил, времени и денег, не получив никакого профита.

Источник

Асинхронность: назад в будущее

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

Асинхронность… Услышав это слово, у программистов начинают блестеть глаза, дыхание становится поверхностным, руки начинают трястись, голос — заикаться, мозг начинает рисовать многочисленные уровни абстракции… У менеджеров округляются глаза, звуки становятся нечленораздельными, руки сжимаются в кулаки, а голос переходит на обертона… Единственное, что их объединяет — это учащенный пульс. Только причины этого различны: программисты рвутся в бой, а менеджеры пытаются заглянуть в хрустальный шар и осознать риски, начинают судорожно придумывать причины увеличения сроков в разы… И уже потом, когда большая часть кода написана, программисты начинают осознавать и познавать всю горечь асинхронности, проводя бесконечные ночи в дебаггере, отчаянно пытаясь понять, что же все-таки происходит…

Именно такую картину рисует мое воспаленное воображение при слове “асинхронность”. Конечно, все это слишком эмоционально и не всегда правда. Ведь так. Возможны варианты. Некоторые скажут, что “при правильном подходе все будет работать хорошо”. Однако это можно сказать всегда и везде при всяком удобном и не удобном случае. Но лучше от этого не становится, баги не исправляются, а бессонница не проходит.

Так что же такое асинхронность? Почему она так привлекательна? А главное: что с ней не так?

Введение

Асинхронность на текущий момент является достаточно популярной темой. Достаточно просмотреть последние статьи на хабре, чтобы в этом убедиться. Тут тебе и обзор различных библиотек, и использования языка Go, и всякие асинхронные фреймворки на JS, и много чего другого.

Обычно асинхронность используется для сетевого программирования: всякие сокеты-шмокеты, читатели-писатели и прочие акцепторы. Но бывают еще забавные и интересные события, особенно в UI. Здесь я буду рассматривать исключительно сетевое использование. Однако, как будет показано в следующей статье, подход можно расширять и углублять в неведомые дали.

Чтобы быть совсем уж конкретным, будем писать простой HTTP сервер, который на некий любой запрос посылает некий стандартный ответ. Это чтоб не писать парсер, т.к. к теме асинхронности он имеет ровно такое же отношение, как положение звезд к характеру человека (см. астрологию).

Синхронный однопоточный сервер

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

Хм. Синхронный? А при чем тут синхронный, спросит внимательный читатель, открыв статью про асинхронность. Ну, во-первых, надо же с чего-то начать. С чего-то простого. А во-вторых… Короче, я автор, поэтому будет так. А потом и сами узнаете, зачем.

Итак, описание сокета и акцептора:

Ничего лишнего, просто сервер. Socket позволяет писать и читать, в том числе до определенных символов ( readUntil ). Acceptor слушает указанный порт и принимает соединения.

Реализация всего этого хозяйства приведена ниже:

Давайте теперь напишем долгожданный сервер. Вот он:

Синхронный многопоточный сервер

Теперь можно и сервер написать:

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

Асинхронный сервер

В чем проблема предыдущего подхода? А в том, что потоки вместо реальной работы большую часть времени ожидают на событиях из сети, отжирая ресурсы. Хочется как-то более эффективно использовать потоки для выполнения полезной работы.

Ну что ж, вроде пока ничего страшного нет.

Помимо этого необходимо написать аналог go и диспетчеризацию:

Здесь указывается обработчик, который будет запускаться асинхронно в пуле потоков и, собственно, создание пула потоков с последующей диспетчеризацией.

Вот как выглядит реализация:

Здесь мы используем sync::go для создания потоков из синхронного подхода.

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

Вот такая простыня. С каждым новым вызовом растет вложенность лямбд. Обычно, конечно, такое через лямбды не пишут, т.к. есть сложности с зацикливанием: в лямбду необходимо пробрасывать саму себя, чтобы внутри самой себя позвать саму себя. Но тем не менее, читабельность кода будет примерно одинаковая, т.е. одинаково плохая при сравнении с синхронным кодом.

Сопрограммы

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

Итак, чего же нам всем хочется? Счастья, здоровья, денег мешок. А хочется простого: использовать плюсы асинхронного и синхронного подходов одновременно, т.е. чтобы и производительность была как у асинхронного, и простота как у синхронного.

На бумаге звучит замечательно. Возможно ли это? Для ответа на вопрос нам понадобится небольшое введение в сопрограммы.

Вот что такое обычные процедуры? Находимся мы, значит, в каком-то месте исполнения и тут раз, и позвали процедуру. Для вызова сначала запоминается текущее место для возврата, затем зовется процедура, она исполняется, завершается и возвращает управление в то место, откуда была позвана. А сопрограмма — это то же самое, только другое: она тоже возвращает управление в то место, откуда была позвана, но при этом она не завершается, а останавливается в некотором месте, с которого дальше продолжает работать при повторном запуске. Т.е. получается эдакий пинг-понг: вызывающий бросает мячик, сопрограмма ловит его, перебегает в другое место, бросает обратно, вызывающий тоже что-то делает (перебегает) и снова бросает в предыдущее место уже сопрограммы. И так происходит до тех пор, пока сопрограмма не завершится. В целом можно сказать, что процедура — это частный случай сопрограммы.

Как теперь это можно использовать для наших асинхронных задач? Ну тут наводит на мысль то, что сопрограмма сохраняет некий контекст исполнения, что для асинхронности крайне важно. Именно это и буду я использовать: если сопрограмме потребуется выполнить асинхронную операцию, то я просто вызову асинхронный метод и выйду из сопрограммы. А обработчик по завершению асинхронной операции просто продолжит исполнение нашей сопрограммы с места последнего вызова той самой асинхронной операции. Т.е. вся грязная работа по сохранению контекста ложится на плечи реализации сопрограмм.

И вот тут как раз и начинаются проблемы. Дело в том, что поддержка сопрограмм на стороне языков и процессоров — дела давно минувших дней. Для реализации переключения контекстов исполнения сегодня необходимо проделать множество операций: сохранить состояния регистров, переключить стек и заполнить некоторые служебные поля для корректной работы среды исполнения (например, для исключений, TLS и др.). Более того, реализация зависит не только от архитектуры процессора, но еще и от компилятора и операционной системы. Звучит как последний гвоздь в крышку гроба…

Реализация сопрограмм

Итак, для наших целей напишем свои сопрограммы. Интерфейс будет такой:

Вот такой нехитрый интерфейс. Ну и сразу вариант использования:

Должен выдать на экран:

Начнем с метода start :

Здесь boost::context::make_fcontext создает нам контекст и передает в качестве стартовой функции статический метод starterWrapper0 :

Отмечу один интересный момент: если не сохранить обработчик внутри сопрограммы (до его вызова), то при последующем возврате программа может благополучно упасть. Это связано с тем, что, вообще говоря, обработчик хранит в себе некоторое состояние, которое может быть разрушено в какой-то момент.

Теперь осталось рассмотреть остальные функции:

Synca: async наоборот

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

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

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

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

Реализация

Начнем с реализации функции go :

Действия простые: смотрим, есть ли что-то для обработки. Если есть — то выполняем, нет — тогда сопрограмма закончила свою работу и ее можно удалить.

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

Остальные функции реализуются аналогично:

Использование

Перейдем к использованию нашего функционала. Тут все гораздо проще и изящнее:

Но отличие в реализации носит принципиальный характер: получившийся код использует асинхронное сетевое взаимодействие, а значит является гораздо более эффективной реализацией. Собственно на этом наша цель достигнута: сделать симбиоз синхронного и асинхронного подходов, взяв из них самое лучшее, т.е. простоту синхронного и производительность асинхронного.

Улучшение

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

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

И тогда наш сервер перепишется в виде:

Что гораздо проще для понимания и использования.

Вопрос 1. А что с производительностью?

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

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

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

Вопрос 2. Ну допустим. А можно ли таким способом решать более сложные асинхронные задачи?

Теорема. Любую асинхронную задачу можно решить с помощью сопрограмм.

для чего может использоваться асинхронность. Смотреть фото для чего может использоваться асинхронность. Смотреть картинку для чего может использоваться асинхронность. Картинка про для чего может использоваться асинхронность. Фото для чего может использоваться асинхронность

Доказательство.
Вначале возьмем функцию, которая использует асинхронные вызовы. Любую функцию можно превратить в сопрограмму, т.к. функция является частным случаем сопрограммы. Далее возьмем какой-либо асинхронный вызов в такой преобразованной сопрограмме. Такой вызов можно представить в следующем виде:

Рассмотрим случай, когда у нас отсутствует код после вызова:

Такой код с точки зрения сопрограммы эквивалентен следующему:

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

Т.е. на один асинхронный вызов стало меньше. Применяя такой подход к каждому асинхронному вызову функции и к каждой функции мы перепишем весь код на сопрограммах. Ч.т.д.

Выводы

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

В следующей статье будет рассмотрен гораздо более сложный пример, который раскроет всю мощь и потенциал сопрограмм!

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *