Понимание цикла событий

Я думаю об этом, и вот что я придумал:

Допустим, у нас есть такой код:

console.clear();
console.log("a");
setTimeout(function(){console.log("b");},1000);
console.log("c");
setTimeout(function(){console.log("d");},0);

Приходит запрос, и механизм JS начинает выполнять код, описанный выше, шаг за шагом. Первые два вызова являются синхронизированными. Но когда дело доходит до setTimeout метод, это становится асинхронным выполнением. Но JS немедленно возвращается из него и продолжает выполнение, которое называется Non-Blocking или же Async, И это продолжает работать на других и т. Д.

Результаты этого выполнения следующие:

AcDb

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

Здесь речь идет об однопоточном приложении. JS Engine продолжает выполнять это, и если он не завершит первый запрос, он не перейдет ко второму. Но хорошо то, что он не будет ждать блокирующих операций, таких как setTimeout чтобы решить, так будет быстрее, потому что он принимает новые входящие запросы.

Но мои вопросы возникают вокруг следующих пунктов:

# 1: Если речь идет об однопоточном приложении, то какой механизм обрабатывает setTimeouts в то время как движок JS принимает больше запросов и выполняет их? Как отдельный поток продолжает работать над другими запросами? Что работает на setTimeout в то время как другие запросы продолжают поступать и выполняться.

№ 2: если эти setTimeout функции выполняются за кулисами, когда поступает и выполняется больше запросов, что выполняет асинхронное выполнение за кулисами? Что это за вещь, о которой мы говорим, называется EventLoop?

# 3: Но не следует ли помещать весь метод в EventLoop чтобы все это выполнялось и вызывался метод обратного вызова? Вот что я понимаю, когда говорю о функциях обратного вызова:

function downloadFile(filePath, callback)
{
  blah.downloadFile(filePath);
  callback();
}

Но в этом случае, как JS Engine узнает, является ли она асинхронной функцией, чтобы она могла поместить обратный вызов в EventLoop? Perhaps something like theКлючевое слово async` в C# или какой-либо атрибут, указывающий, что метод, который будет использовать JS Engine, является асинхронным методом и должен рассматриваться соответствующим образом.

# 4: Но в статье говорится, что это противоречит тому, что я догадывался о том, как все может работать:

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

# 5: И здесь есть это изображение, которое может быть полезным, но первое объяснение на рисунке говорит о том же, что упоминалось в вопросе № 4:

Итак, мой вопрос здесь, чтобы получить некоторые разъяснения о пунктах, перечисленных выше?

5 ответов

Решение

1: Если мы говорим об однопоточном приложении, то что обрабатывает setTimeouts, в то время как движок JS принимает больше запросов и выполняет их? Разве этот отдельный поток не будет продолжать работать с другими запросами? Тогда, кто будет продолжать работать над setTimeout, пока другие запросы продолжают поступать и выполняться.

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

Теперь о "выполнении setTimeouts", когда вы вызываете setTimeoutвсе, что делает узел - это в основном обновляет структуру данных функций, которые будут выполняться в будущем. Он в основном имеет кучу очередей, которые нужно выполнить, и каждый "тик" цикла событий выбирает одну, удаляет ее из очереди и запускает.

Ключевым моментом для понимания является то, что узел в большинстве случаев использует ОС. Таким образом, входящие сетевые запросы фактически отслеживаются самой ОС, и когда узел готов обработать один, он просто использует системный вызов, чтобы запросить у ОС сетевой запрос с данными, готовыми для обработки. Большая часть IO "рабочего" узла выполняет либо "Привет, ОС, у вас есть сетевое соединение с данными, готовыми для чтения?" или "Привет, ОС, у кого-нибудь из моих ожидающих вызовов файловой системы есть данные, готовые?". Основываясь на своем внутреннем алгоритме и дизайне механизма цикла событий, узел выберет один "тик" JavaScript для выполнения, запустит его, а затем повторите процесс снова и снова. Вот что подразумевается под циклом событий. Node в основном все время определяет "какой следующий маленький кусочек JavaScript мне нужно запустить?", А затем запускает его. Это факторы, при которых завершается ввод-вывод ОС, и вещи, поставленные в очередь в JavaScript через вызовы setTimeout или же process.nextTick,

2: Если эти setTimeout будут выполняться за кулисами, в то время как все больше и больше запросов поступают и выполняются, вещь выполняет асинхронные выполнения за кулисами - это та, о которой мы говорим о EventLoop?

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

3: Как JS Engine может узнать, является ли она асинхронной функцией, чтобы она могла поместить ее в EventLoop?

В ядре узла есть фиксированный набор функций, которые являются асинхронными, потому что они выполняют системные вызовы, и узел знает, что это, потому что они должны вызывать ОС или C++. По сути, все операции ввода-вывода в сети и файловой системе, а также взаимодействия с дочерними процессами будут асинхронными, и ЕДИНСТВЕННЫЙ способ, которым JavaScript может заставить узел выполнять что-то асинхронно, заключается в вызове одной из асинхронных функций, предоставляемых базовой библиотекой узла. Даже если вы используете пакет npm, который определяет его собственный API, для создания цикла событий, в конечном счете, код этого пакета npm вызовет одну из асинхронных функций ядра узла, и тогда узел узнает, что тик завершен, и он может запустить событие алгоритм цикла снова.

4 Event Loop - это очередь функций обратного вызова. Когда выполняется асинхронная функция, функция обратного вызова помещается в очередь. Механизм JavaScript не начинает обрабатывать цикл обработки событий, пока не будет выполнен код после выполнения асинхронной функции.

Да, это правда, но это вводит в заблуждение. Главное, что нормальный шаблон:

//Let's say this code is running in tick 1
fs.readFile("/home/barney/colors.txt", function (error, data) {
  //The code inside this callback function will absolutely NOT run in tick 1
  //It will run in some tick >= 2
});
//This code will absolutely also run in tick 1
//HOWEVER, typically there's not much else to do here,
//so at some point soon after queueing up some async IO, this tick
//will have nothing useful to do so it will just end because the IO result
//is necessary before anything useful can be done

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

Есть фантастическое видеоурок от Филиппа Робертса, который объясняет цикл событий javascript самым простым и концептуальным способом. Каждый разработчик JavaScript должен взглянуть.

Вот ссылка на видео на Youtube.

Не думайте, что хост-процесс является однопоточным, это не так. То, что является однопоточным, это часть процесса хоста, который выполняет ваш код JavaScript.

За исключением фоновых работников, но это усложняет сценарий...

Итак, весь ваш js-код выполняется в одном и том же потоке, и нет никакой возможности, что вы получите две разные части js-кода для одновременной работы (так что вы не будете управлять ни одним параллелизмом).

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

Вот мое мысленное представление (будьте осторожны: просто я не знаю деталей реализации браузера!) Вашего примера кода:

console.clear();                                   //exec sync
console.log("a");                                  //exec sync
setTimeout(                //schedule inAWhile to be executed at now +1 s 
    function inAWhile(){
        console.log("b");
    },1000);    
console.log("c");                                  //exec sync
setTimeout(
    function justNow(){          //schedule justNow to be executed just now
        console.log("d");
},0);       

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

Когда ваш код завершается, он удаляется из цикла событий, и хост-процесс возвращается к его проверке, чтобы увидеть, есть ли еще код для запуска. Цикл обработки событий содержит еще два обработчика событий: один должен быть выполнен сейчас (функция justNow), а другой - через секунду (функция inAWhile).

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

Loop Event имеет одну простую работу - следить за Call Stack, то обратный вызов очереди и очереди задач Micro. Если стек вызовов пуст, цикл событий примет первое событие из очереди микрозадач, затем из очереди обратных вызовов и отправит его в стек вызовов, который эффективно его запускает. Такая итерация называется галочкой в ​​Цикле событий.

Как известно большинству разработчиков, то, что Javascript является однопоточным, означает, что два оператора в javascript не могут выполняться параллельно, что является правильным. Выполнение происходит построчно, что означает, что все операторы javascript синхронны и блокируются. Но есть способ запустить ваш код асинхронно, если вы используете функцию setTimeout(), веб-API, предоставляемый браузером, который гарантирует, что ваш код выполняется по истечении указанного времени (в миллисекундах).

Пример:

      console.log("Start");

setTimeout(function cbT(){
console.log("Set time out");
},5000);

fetch("http://developerstips.com/").then(function cbF(){
console.log("Call back from developerstips");
});

// Millions of line code
// for example it will take 10000 millisecond to execute

console.log("End");

setTimeout принимает функцию обратного вызова в качестве первого параметра и время в миллисекундах в качестве второго параметра. После выполнения вышеуказанного оператора в консоли браузера он напечатает

      Start
End
Call back from developerstips
Set time out

Примечание. Ваш асинхронный код запускается после завершения выполнения всего синхронного кода.

Понять, как выполняется код построчно

Движок JS выполнит 1-ю строку и напечатает "Пуск" в консоли.

Во второй строке он видит функцию setTimeout с именем cbT, а движок JS подталкивает функцию cbT в очередь callBack.

После этого указатель переместится прямо на строку № 7, и там он увидит обещание, а JS-движок отправит функцию cbF в очередь микрозадач.

Затем он выполнит Миллионы строкового кода и в конце напечатает «Конец».

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

JavaScript - это высокоуровневый однопоточный язык с интерпретацией. Это означает, что ему нужен интерпретатор, который преобразует код JS в машинный код. интерпретатор означает двигатель. Двигатели V8 для хрома и webkit для сафари. Каждый движок содержит память, стек вызовов, цикл событий, таймер, веб-API, события и т. Д.

Цикл событий: микрозадачи и макрозадания

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

Задачи установлены - движок обрабатывает их - затем ожидает новых задач (в спящем режиме и почти нулевом потреблении ЦП). Может случиться так, что задача приходит, когда движок занят, а затем ставится в очередь. Задачи образуют очередь, так называемую « очередь макрозадач ».

Микрозадачи исходят исключительно из нашего кода. Обычно они создаются с помощью обещаний: выполнение обработчика .then / catch / finally становится микрозадачей. Микрозадачи также используются «под прикрытием» await, поскольку это еще одна форма обработки обещаний. Сразу после каждой макрозадачи движок выполняет все задачи из очереди микрозадач до запуска любых других макрозадач, рендеринга или чего-либо еще.

Другие вопросы по тегам