Найти в Дзене
Nuances of programming

Однопоточность и асинхронность: как у Node это получается?

Оглавление

Источник: Nuances of Programming

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

Node.js  —  среда выполнения JavaScript, которая позволяет анализировать, компилировать и запускать JavaScript-код. Node делает это с помощью движка с открытым исходным кодом V8 от Google, написанного на C++.

С движком V8 Node может “под капотом», скрытно для пользователя, выполнять как JavaScript, так и C++. Это позволяет писать как синхронный, так и асинхронный JavaScript-код в однопоточной среде, не беспокоясь о потоковой передаче или параллелизме.

Цикл событий

Цикл событий  —  вот что дает приложениям Node возможность работать в одном потоке, но при этом поддерживает асинхронные операции и неблокирующий ввод-вывод. Для понимания функциональности цикла событий важно знать, что такое стек вызовов, очередь сообщений и API C++.

Компоненты, необходимые для обработки параллелизма
Компоненты, необходимые для обработки параллелизма

Стек вызовов  —  это преимущественно LIFO-стек (Last In, First Out, “последний на вход, первый на выход”), который отслеживает, какая задача будет выполняться следующей в основном потоке. Задачи, определенные в вашем JavaScript-коде, помещаются в этот стек при выполнении кода. Посмотрим, как через стек вызовов выполняется приведенный ниже код:

const bar = () => console.log("bar")

const baz = () => console.log("baz")

const foo = () => {
console.log("foo")
bar()
baz()
}

foo()

Данный код предназначен для простой синхронной программы и поэтому не требует участия API C++ или очереди сообщений.

-3

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

foo bar
baz

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

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
console.log('foo')
setTimeout(bar, 0)
baz()
}

foo()

Вывод на консоль будет таким:

foo baz
bar

Причина странного порядка в console.log  —  в том, что Node выполняет setTimeout как асинхронную операцию, даже если минимальное время ожидания setTimeout равно нулю миллисекунд.

Выполнение приведенного выше кода
Выполнение приведенного выше кода

Node выгружает асинхронные задачи из стека вызовов в API C++ и выполняет их, задействуя системное ядро. Большинство системных ядер многопоточны и могут в фоновом режиме выполнять сразу несколько задач.

После завершения асинхронной задачи соответствующая функция обратного вызова помещается в очередь сообщений. Это очередь FIFO (First In  —  First Out, “первым на вход, первым на выход”), которая сохраняет правильную последовательность выполнения функций обратного вызова.

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

Очередь задач

Очередь задач была введена в Javascript ES6. Она похожа на очередь сообщений, но здесь асинхронным задачам не нужно ждать, пока все задачи в стеке вызовов будут выполнены. Это позволяет выполнить результат асинхронной задачи сразу же, как только завершится текущая задача из стека вызовов.

Функциональность промисов в JavaScript основана на очереди задач.

Заключение

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

Спасибо за чтение!

Читайте также:

Читайте нас в Telegram, VK

Перевод статьи: Janith Gamage, “Single-Threaded and Asynchronous — How Does Node Do It?”