Программирование • 22 июля 2024 • 5 мин чтения

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

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

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

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

Рассмотрим его основные части и их функции.

Устройство управления (УУ), или блок управления — что-то вроде дирижёра, который управляет оркестром, чтобы музыканты играли синхронно. УУ управляет порядком выполнения инструкций, получаемых из памяти. Оно декодирует инструкции и направляет их в соответствующие части процессора.
Арифметико-логическое устройство (АЛУ) — это как калькулятор внутри процессора, который быстро выполняет математические, логические, информационные операции, а также операции перехода и останова.
Регистры — небольшие ячейки памяти внутри процессора, которые используются для временного хранения данных и инструкций во время их обработки. Это как карманы, куда процессор кладёт нужные вещи на короткое время. Большая группа регистров представляет собой оперативные запоминающие устройства — ОЗУ (от англ. RAM). Память у такого хранилища непостоянная, и данные оттуда пропадают при отключении питания.
Кэш-память — это память, которая хранит часто используемые данные и инструкции. Это нужно, чтобы ускорить работу процессора. Каждую секунду он обрабатывает миллиарды инструкций. Если бы они хранились в ОЗУ, то каждый раз вытаскивать их оттуда занимало бы много времени, и программы бы тормозили.

Процессор работает циклами: загружает инструкцию из оперативной памяти (ОЗУ) → читает инструкцию → выполняет её на уровне АЛУ → записывает результат в регистры или оперативную память

Пока включен компьютер, циклы выполнения инструкций повторяются. Чтобы это происходило без задержек, в процессор впаян специальный счётчик, который отсчитывает каждый шаг цикла, или такт. Поэтому ключевая характеристика процессора — это тактовая частота, то есть его скорость. У современных компьютеров, на которых можно запускать сложные игры и приложения, тактовая частота достигает 4,5 ГГц. Это значит, что каждую секунду счётчик отмеряет четыре с половиной миллиарда операций. В это время пользователь компьютера наслаждается скоростью загрузки программ, стриминговыми сервисами с музыкой и кино и плавными движениями персонажей в играх.

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

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

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

Асинхронность реализуется на нескольких уровнях — от написания кода до выполнения инструкций процессором:

1. Уровень кода. Разработчики используют языки программирования и библиотеки для создания асинхронного кода. Это и есть инструкция, которая говорит программе выполнять другие задачи, пока не завершилась долгая операция.
2. Уровень рантайма. Рантайм, или среда выполнения кода, интерпретирует асинхронный код и управляет задачами. Асинхронные задачи добавляются в очередь, и событийный цикл следит за их выполнением, передавая управление соответствующим обработчикам, когда задачи завершаются.
3. Уровень операционной системы. Операционная система (ОС) играет ключевую роль в реализации асинхронности, предоставляя необходимые механизмы для управления выполнением задач. У ОС есть планировщик, который распределяет процессорное время между различными задачами. Асинхронные задачи могут быть помещены в очереди ОС, чтобы быть выполненными, когда процессор доступен. ОС использует прерывания и системные вызовы для выполнения асинхронных операций ввода-вывода. Например, когда данные готовы для чтения с диска, прерывание сообщает ОС, что выполнение задачи может быть продолжено.
4. Уровень железа. На самом нижнем уровне асинхронность поддерживается аппаратными средствами — процессором и оперативной памятью. Процессор может переключаться между задачами. Это позволяет выполнять другие задачи, пока одна операция (например, ожидание данных из сети) ждёт завершения. Некоторые операции вроде чтения и записи данных могут использовать прямой доступ к памяти и таким образом освобождать процессор для других задач.

Путь асинхронной задачи от кода до железа

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

Асинхронное программирование используется там, где нужно выполнять длительные операции без заморозки основного потока выполнения программы. Вот несколько примеров:

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

Использовать асинхронность в веб-проектах учат на курсе «Фронтенд-разработчик». Студенты начинают с азов — вёрстки страниц с помощью HTML и CSS. Потом переходят к изучению JavaScript. К концу обучения разрабатывают корпоративные системы в команде с менеджерами, аналитиками и дизайнерами — всё как в настоящих проектах.

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

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

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

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

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

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

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

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

Проблема многопоточности — в управлении. Если один поток должен ждать, пока прочитаются данные с диска, он блокируется и не может выполнять другие задачи. Бывают и конфликты: например, когда два потока пытаются одновременно изменить одну и ту же переменную. Всё это приводит к неэффективному использованию ресурсов и сложностям в отладке кода.

Асинхронность — это альтернативный подход к выполнению долгих операций без блокировки потоков:

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

В итоге разработчику проще управлять кодом: не нужно координировать потоки.

Пример асинхронности

Асинхронность поддерживают многие современные языки программирования. Например, JavaScript, Java, Python, C#, Go, Ruby.

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

JavaScript поддерживает асинхронность через несколько механизмов:

Callbacks — функция обратного вызова. Колбэки возвращают результат не сразу, а через некоторое время — после того, как завершится какая-нибудь операция (например, загрузка фотографии или анимации). В этот момент пользователь уже видит текст страницы, а не ждёт, пока он загрузится вместе с другим контентом.
Promises — способ работы с асинхронными операциями, который делает код проще и понятнее, чем с колбэками. Promises заменяют цепочки вложенных колбэков, которые могут быть сложными для чтения и понимания. С promise код выглядит более линейно: сначала создаём обещание (promise), которое выполняется (resolve) или отклоняется (reject) при завершении асинхронной операции. Вместо вложенных колбэков просто используют методы .then() и .catch(), чтобы указать, что делать с результатом или ошибкой.
Async/Await — операторы JS, которые упрощают работу с promises. Это самый современный способ. Когда используют async перед функцией, она автоматически возвращает promise, а внутри этой функции можно использовать await перед асинхронной операцией, чтобы подождать её завершения. Это позволяет писать асинхронный код в виде последовательных шагов, как если бы они были синхронными, без использования .then(). В результате код становится более читаемым и компактным.

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

Как это реализовано на JavaScript:

1. Отправка запроса на сервер. Когда вызывается функция fetchNews, она начинает выполнение. Запрос fetch('https://api.example.com/latest-news') отправляется на сервер, чтобы получить список последних новостей. Это асинхронная операция, поэтому JavaScript не ждёт её завершения и продолжает выполнять другие задачи.

async function fetchNews() {

const response = await
fetch('https://api.example.com/latest-news');

2. Ожидание ответа. Используем await, чтобы подождать завершения запроса. Это приостанавливает выполнение функции fetchNews до получения ответа от сервера. Если сервер успешно отвечает, ответ сохраняется в переменную response. Проверяем, успешен ли ответ, с помощью response.ok. Если ответ не успешен, выбрасываем ошибку.

if (!response.ok) {

throw new Error('Network response was not ok');

}

3. Обработка данных. Используем await response.json(), чтобы преобразовать ответ в формат JSON. Это тоже асинхронная операция. Полученные данные затем передаются в функцию displayNews.

const data = await response.json();

displayNews(data);

}

4. Результат запроса отображается на странице. Функция displayNews проходит по массиву новостей и добавляет каждую новость на страницу, создавая новые элементы на веб-странице.

function displayNews(news) {

const newsContainer =
document.getElementById('news-container');

const fragment =
document.createDocumentFragment();


news.forEach(item => {

const newsItem =
document.createElement('div');

newsItem.textContent = item.title;

fragment.appendChild(newsItem);

});

newsContainer.appendChild(fragment);

}

Совет эксперта

Руслан Посевкин
Асинхронное программирование может показаться сложным, но не бойтесь пробовать разные подходы. При работе с JavaScript это колбэки, промисы и async/await. Практикуйтесь и изучайте примеры, чтобы лучше понять, как это работает. Не забывайте обрабатывать ошибки — это важно для создания надёжных программ. Следите за лучшими практиками и новыми тенденциями в разработке, чтобы использовать самые эффективные и современные методы.
Статью подготовили:
Руслан Посевкин
Яндекс Практикум
Frontend Engineer
Яндекс Практикум
Редактор
Полина Овчинникова
Яндекс Практикум
Иллюстратор

Дайджест блога: ежемесячная подборка лучших статей от редакции

Поделиться

Успейте начать учебу в Практикуме до конца ноября со скидкой 20%

Mon Oct 07 2024 14:05:14 GMT+0300 (Moscow Standard Time)