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

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

Скажем, у нас есть такой код:

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. И он продолжает работать над другими и т.д.

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

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 Engine знает, является ли он функцией async, чтобы он мог поместить обратный вызов в ключевое слово EventLoop? Perhaps something like the async` в С# или какой-то атрибут, который указывает на метод JS Engine, взять метод асинхронный, и его следует обрабатывать соответствующим образом.

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

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

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

enter image description here

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

Ответ 1

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

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

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

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

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

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

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

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

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

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

//Let 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 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

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

Ответ 2

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

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

Ответ 3

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

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

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

Выполняемый 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 и т.д....