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


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

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

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

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

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

a c d b

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

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

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

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

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

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

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

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

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

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

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

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

3 109

3 ответа:

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

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

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

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

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

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

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

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

4 цикл событий представляет собой очередь функций обратного вызова. При выполнении асинхронной функции функция обратного вызова помещается в очередь. Механизм 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-кода для одновременного выполнения (таким образом, вы получаете not concurrency nigthmare to руководить.)

выполняемый код 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 s прошло, он запускает функцию inAWhile и так далее....