Что такое асинхронность в программировании
Перейти к содержимому

Что такое асинхронность в программировании

  • автор:

Синхронное и асинхронное программирование: в чем разница?

Синхронный и асинхронный классифицируют два разных стиля программирования, каждый со своими преимуществами и недостатками. А какая будет разница между ними? Давайте разбираться

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

Что такое асинхронное программирование?
Асинхронное программирование основано на неблокирующем протоколе ввода-вывода (I/O). Это означает, что асинхронная программа не выполняет операции в иерархическом или последовательном порядке. Получающееся в результате распараллеливание означает, что асинхронная программа может обрабатывать несколько запросов одновременно и независимо.

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

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

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

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

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

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

Масштабируемость
Async/await — это хорошо известная синтаксическая структура многих языков программирования, которая позволяет асинхронным функциям удобно работать с промисами. Промисы — это объекты, которые инкапсулируют желаемое поведение асинхронной операции. Обе эти концепции являются неотъемлемой частью понятия масштабируемости. Масштабируемость может осуществляться двумя способами — горизонтально и вертикально.

Синхронные и асинхронные программы могут использовать горизонтальное масштабирование, добавляя дополнительные серверы для масштабирования служб. С другой стороны, вертикальное масштабирование — это более простой процесс, который могут использовать асинхронные программы, чтобы позволить одному серверу увеличить количество запросов, которые может обработать сервер. Подключая асинхронную функцию с помощью ключевого слова async/await, разработчики могут упростить программы, которые в противном случае были бы синхронными, используя методы обратного вызова на основе обещаний.

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

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

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

Совместимость
C++ и JavaScript — самые выдающиеся языки программирования, поддерживающие асинхронное программирование. В этих языках ключевое слово async широко используется и почитается. Но с другими языками дело обстоит не так просто. Хотя, безусловно, можно программировать асинхронные программы практически на любом языке, это будет трудоемкой задачей, если такая реализация не будет предварительно оснащена рассматриваемым языком.

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

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

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

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

Маркетинговый потенциал
Поисковым системам легче сканировать веб-страницы, использующие традиционную синхронную архитектуру. Для маркетологов, которые зависят от поисковой оптимизации (SEO) для создания своей репутации и узнаваемости бренда, это заметное преимущество. Чем больше людей просматривают ваш веб-сайт через Google или Bing, тем больше посетителей будет на вашей веб-странице. Естественно, это положительно скажется на вашем возврате инвестиций (ROI).

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

Скорость
Время загрузки может быть медленнее при синхронном программировании по сравнению с асинхронным программированием. Этого следовало ожидать, учитывая то, как синхронные программы обрабатывают несколько запросов. Когда поток блокируется, другие потоки в очереди также блокируются. Проще говоря, синхронное программирование похоже на посещение Disney World без VIP-пропуска .

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

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

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

Когда использовать синхронное программирование
Как известно, компьютеры работают быстро. Таким образом, синхронное программирование занимает не так много времени, как вы можете себе представить. Если вы просто хотите разработать внешнее приложение или выполнить базовую функцию центрального процессора (ЦП), то асинхронное программирование выходит за рамки допустимого. Рендеринг видео или математические вычисления, например, используют центральный процессор для максимальной функциональности. Использование асинхронного программирования для этих типов задач перегрузило бы ЦП и принесло бы больше вреда, чем пользы.

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

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

Для чего нужна асинхронность?

Нам всем нравится, как Rust позволяет нам писать быстрое и безопасное программное обеспечение. Но как асинхронное программирование вписывается в это видение?

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

Асинхронность и другие модели параллелизма

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

  • Потоки ОС не требуют каких-либо изменений в модели программирования, что упрощает реализацию параллелизма. Однако синхронизация между потоками может быть затруднена, а издержки производительности велики. Пулы потоков могут снизить некоторые из этих затрат, но не настолько, чтобы поддерживать огромные рабочие нагрузки, связанные с вводом-выводом.
  • Программирование, управляемое событиями (event-driven programming), в сочетании с обратными вызовами (callbacks) может быть очень эффективным, но приводит к многословному, «нелинейному» потоку управления. Поток данных и распространение ошибок часто трудно отслеживать.
  • Корутины (Coroutines), как и потоки, не требуют изменений в модели программирования, что делает их простыми в использовании. Как и асинхронность, они также могут поддерживать большое количество задач. Однако они абстрагируются от низкоуровневых деталей, важных для системного программирования и разработчиков пользовательских сред выполнения.
  • Модель акторов делит все параллельные вычисления на единицы, называемые акторами, которые взаимодействуют посредством передачи ошибочных сообщений, как в распределённых системах. Модель акторов может быть эффективно реализована, но она оставляет без ответа многие практические вопросы, такие как управление потоком и логика повторных попыток.

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

Асинхронность в Rust против других языков

Хотя асинхронное программирование поддерживается на многих языках, некоторые детали зависят от реализации. Реализация асинхронности в Rust отличается от большинства языков несколькими способами:

  • Футуры инертны в Rust и работают только при опросе. Сбрасывание футуры останавливает её дальнейший прогресс.
  • Асинхронность в Rust бесплатна (zero-cost), а это значит, что вы платите только за то, что используете. В частности, вы можете использовать асинхронность без распределения кучи и динамической диспетчеризации, что отлично подходит для производительности! Это также позволяет использовать асинхронность в средах с ограничениями, таких как встроенные системы.
  • В Rust нет встроенной среды выполнения асинхронности. Вместо этого такие среды предоставляются трейтами, поддерживаемыми сообществом.
  • В Rust доступны как однопоточные, так и многопоточные среды выполнения, которые имеют разные сильные и слабые стороны.

Асинхронность против потоков в Rust

Основной альтернативой асинхронности в Rust является использование потоков ОС либо напрямую через std::thread , либо косвенно через пул потоков. Переход от потоков к асинхронному или наоборот обычно требует серьёзной работы по рефакторингу как с точки зрения реализации, так и (если вы создаёте библиотеку) любых открытых общедоступных интерфейсов. Таким образом, ранний выбор модели, которая соответствует вашим потребностям, может сэкономить много времени на разработку.

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

Асинхронность значительно снижает нагрузку на ЦП и память, особенно для рабочих нагрузок с большим количеством задач, связанных с вводом-выводом, таких как серверы и базы данных. При прочих равных у вас может быть на порядки больше задач, чем потоков ОС, потому что асинхронная среда выполнения использует небольшое количество (дорогих) потоков для обработки большого количества (дешёвых) задач. Однако асинхронный Rust приводит к большим двоичным объектам из-за конечных автоматов, сгенерированных из асинхронных функций, и поскольку каждый исполняемый файл включает в себя асинхронную среду выполнения.

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

Пример: одновременная загрузка

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

fn get_two_sites() < // Spawn two threads to do work. let thread_one = thread::spawn(|| download("https://www.foo.com")); let thread_two = thread::spawn(|| download("https://www.bar.com")); // Wait for both threads to complete. thread_one.join().expect("thread one panicked"); thread_two.join().expect("thread two panicked"); >

Однако загрузка веб-страницы — несложная задача; создание потока для такого небольшого объёма работы довольно расточительно. Для более крупного приложения это может легко стать узким местом. В асинхронном Rust мы можем выполнять эти задачи одновременно без дополнительных потоков:

async fn get_two_sites_async() < // Create two different "futures" which, when run to completion, // will asynchronously download the webpages. let future_one = download_async("https://www.foo.com"); let future_two = download_async("https://www.bar.com"); // Run both futures to completion at the same time. join!(future_one, future_two); >

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

Пользовательские модели параллелизма в Rust

И наконец, Rust не заставляет вас выбирать между потоками и асинхронностью. Вы можете использовать обе модели в одном и том же приложении, что может быть полезно, когда у вас есть смешанные многопоточные и асинхронные зависимости. На самом деле вы даже можете использовать другую модель параллелизма, например программирование, управляемое событиями (event-driven programming), если найдёте библиотеку, которая её реализует.

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

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

Обложка поста Асинхронность в программировании

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

while (true) < std::string data; auto socket = Socket(localhost, port); socket.wait_connection(); while (!socket.end_of_connection()) < data = socket.read(); // Блокировка socket.write(data); // Блокировка >> 

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

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

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

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

Callbacks

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

while (true) < auto socket = Socket(localhost, port); socket.wait_connection(); // Всё ещё есть блокировка socket.async_read([](auto &data) /* * Поток не блокируется, лямбда-функция будет вызвана * каждый раз после получения новых данных из сокета, * а основной поток пойдёт создавать новый сокет и * ждать новое соединение. */ < socket.async_write(data, [](auto &socket) < if (socket.end_of_connection()) socket.close(); >); >); > 

В wait_connection() мы всё ещё ждём чего-то, но теперь вместе с этим внутри функции wait_connection() может быть реализовано подобие планировщика ОС, но с callback-функциями (пока мы ждём нового соединения, почему бы не обработать старые? Например, через очередь). Callback-функция вызывается, если в сокете появились новые данные — лямбда в async_read() , либо данные были записаны — лямбда в async_write() .

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

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

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

Async/Await

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

public async void work() < var db_conn = Db_connection(localhost); var socket = Socket(localhost, port); socket.wait_connection(); var data = socket.async_read(); var db_data = db_conn.async_get(await data); var file_data = File(await data).async_read(); await socket.async_write($””); socket.close(); > 

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

  • Ключевое слово async в заголовке функции говорит компилятору, что функция асинхронная и её нужно компилировать по-другому. Каким именно образом он будет это делать, написано ниже.
  • Первые три строки функции: создание и ожидание соединения.
  • Следующая строка делает асинхронное чтение, не прерывая основной поток исполнения.
  • Следующие две строки делают асинхронный запрос в базу данных и чтение файла. Оператор await приостанавливает текущую функцию, пока не завершится выполнение асинхронной задачи чтения из БД и файла.
  • В последних строках производится асинхронная запись в сокет, но лишь после того, как мы дождёмся асинхронного чтения из БД и файла.

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

Корутины

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

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

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

По сути корутинами называются функции, имеющие несколько точек входа и выхода. У обычных функций есть только одна точка входа и несколько точек выхода. Если вернуться к примеру выше, то первой точкой входа будет сам вызов функции оператором asynс , затем функция прервёт своё выполнение вместо ожидания БД или файла. Все последующие await будут не запускать функцию заново, а продолжать её исполнение в точке предыдущего прерывания. Да, во многих языках в корутине может быть несколько await ’ов.

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

def async_factorial(): result = 1 while True: yield result result *= i fac = async_factorial() for i in range(42): print(next(fac)) 

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

Функция async_factorial() вернёт объект-генератор, который можно передать в функцию next() , а она продолжит выполнение корутины до следующего оператора yield с сохранением состояния всех локальных переменных функции. Функция next() возвращает то, что передаёт оператор yield внутри корутины. Таким образом, функция async_factorial() в теории имеет несколько точек входа и выхода.

Stackful и Stackless

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

Так как в корутинах мы можем в любом месте поставить оператор yield , нам необходимо где-то сохранять весь контекст функции, который включает в себя фрейм на стеке (локальные переменные) и прочую метаинформацию. Это можно сделать, например, полной подменой стека, как это делается в stackful корутинах.

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

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

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

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

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

def fib(): a = 0 b = 1 while True: yield a a += b yield b b += a 

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

class fib: def __init__(self): self.a = 0 self.b = 1 self.__result: int self.__state = 0 def __next__(self): while True: if self.__state == 0: self.a = 0 self.b = 1 if self.__state == 0 or self.__state == 3: self.__result = self.a self.__state = 1 return self.__result if self.__state == 1: self.a += self.b self.__result = self.b self.__state = 2 return self.__result if self.__state == 2: self.b += a self.__state = 3 break 

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

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

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

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

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

Вывод

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

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

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

«IT-специалист с нуля» наш лучший курс для старта в IT

Когда применяется асинхронность

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

Профессия / 8 месяцев
IT-специалист с нуля

Попробуйте 9 профессий за 2 месяца и выберите подходящую вам

vsrat_7 1 (1)

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

  1. Непрерывно выполняют большое количество заданий — среди множества операций всегда будет та, которая должна работать, когда остальные блокируются.
  2. Задания выполняют множество операций по вводу и выводу информации, заставляя синхронную программу впустую тратить время на блокировки.
  3. Задания в основном независимы друг от друга, и необходимость обмена данными между операциями практически отсутствует.

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

Что такое многопоточность

Многопоточность — это параллельное выполнение нескольких блоков (потоков) приложения независимо друг от друга. Например, платформа Node.js использует два вида потоков.

Основной. Обрабатывается циклом событий.

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

Блокировка кода в синхронных приложениях

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

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

Проиллюстрируем блокировку на примере исполнения кода:

const btn = document.querySelector(‘button’);
btn.addEventListener(‘click’, () => let myDate;
for(let i = 0; i < 5000000; i++) let date = new Date();
myDate = date
>
console.log(myDate);
let pElem = document.createElement(‘p’);
pElem.textContent = ‘Это новый параграф.’;
document.body.appendChild(pElem);
>);

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

Это происходит потому, что JavaScript — однопоточный язык. Он выполняет все задачи (потоки) последовательно. Некоторые программные платформы, например языки семейства .NET или Go, создавались с расчетом на асинхронное программирование и многопоточную обработку данных. Однако JavaScript разрабатывался для выполнения простых действий вроде обработки нажатий кнопок на веб-странице. Со временем разработчики создали несколько способов реализовать асинхронную обработку данных в JavaScript-приложениях.

Профессия / 9 месяцев
Frontend-разработчик

Создавайте интерфейсы сервисов, которыми пользуются все

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

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