Розуміння циклу подій


135

Я думаю про це, і ось що я придумав:

Давайте подивимось цей код нижче:

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? Можливо, щось на кшталт asyncключового слова в C # або якийсь атрибут, який вказує на метод JS Engine, який застосовується, - це метод асинхронізації, і до нього слід звертатися відповідно.

# 4: Але стаття говорить зовсім протилежне тому, що я здогадувався про те, як все може працювати:

Цикл подій - це черга функцій зворотного виклику. Коли виконується функція асинхронізації, функція зворотного дзвінка висувається в чергу. Двигун JavaScript не починає обробляти цикл подій, поки код не буде виконаний функцією асинхронізації.

# 5: І ось це зображення може бути корисним, але перше пояснення на зображенні говорить саме те саме, що було зазначено у питанні № 4:

введіть тут опис зображення

Тож моє запитання тут полягає в тому, щоб отримати деякі роз’яснення щодо перелічених вище предметів?


1
Нитки не є правильною метафорою для вирішення цих проблем. Мисліть події.
Denys Séguret

1
@dystroy: Зразок коду для ілюстрації метафори події в JS було б приємно бачити.
Тарік

Я не бачу, що саме тут у вашому питанні.
Denys Séguret

1
@dystroy: Моє запитання тут полягає в тому, щоб отримати уточнення щодо перелічених вище предметів?
Тарік

2
Вузол не є однопоточним, але це не має значення для вас (за винятком того, що йому вдається виконати інші речі, поки виконується ваш код користувача). За один раз виконується лише один зворотний дзвінок у вашому коді користувача.
Denys Séguret

Відповіді:


85

1: Якщо ми говоримо про однопотокове додаток, то що обробляє setTimeouts, коли двигун JS приймає більше запитів і виконує їх? Чи не єдина нитка продовжуватиме працювати над іншими запитами? Тоді хто буде продовжувати працювати над setTimeout, тоді як інші запити продовжують надходити та виконуватись.

У процесі вузла є лише 1 нитка, яка фактично виконує JavaScript вашої програми. Однак, всередині самого вузла, насправді є декілька потоків, що управляють механізмом циклу подій, і це включає пул потоків вводу-виводу та декілька інших. Ключовим є те, що кількість цих потоків не відповідає кількості одночасних з'єднань, які обробляються так, як це було б у моделі одночасності з'єднання "за потоком на з'єднання".

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

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

2: Якщо ці setTimeout будуть виконуватись за кадром, поки надходить більше запитів і відбувається, і вони виконуються, що виконується виконання асинхронних кадрів за лаштунками, це те, про що ми говоримо про EventLoop?

Жоден JavaScript не виконується за кадром. Весь JavaScript у вашій програмі працює спереду та по центру, по одному. Що відбувається за лаштунками, це операційна система, яка обробляє IO, і вузол чекає, що вона буде готова, і вузол керує чергою javascript, що чекає виконання.

3: Як JS Engine може знати, чи це функція асинхронізації, щоб вона могла помістити її в EventLoop?

У ядрі вузла є фіксований набір функцій, які є асинхронними, оскільки вони здійснюють системні дзвінки, і вузол знає, що це, тому що їм потрібно викликати ОС або C ++. В основному вся мережа та файлова система, а також взаємодії дочірніх процесів будуть асинхронними, і ТОЛЬко так, щоб 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 повинен отримати цикл подій протягом певного розумного часу або загальна архітектура не вдається.


1
Скажімо, у мене є черга, яка займе сервер на 1 хвилину для виконання, і перше, що було деякою функцією асинхронізації, яка закінчилася через 10 секунд. Чи піде це до кінця черги чи він підштовхне себе до рядка, щойно він буде готовий?
ilyo

4
Як правило, він піде до кінця черги, але семантика process.nextTickvs setTimeoutvs setImmediateє дещо різною, хоча насправді вам не доведеться піклуватися. У мене є повідомлення в блозі під назвою setTimeout та друзі , в якому детальніше.
Пітер Ліонс

Чи можете ви, будь ласка, докладно? Скажімо, у мене є два зворотні виклики, і перший має метод changeColor з часом виконання 10mS та setTimeout 1 хвилину, а другий має метод changeBackground із часом виконання 50мс із setTimeout 10 секунд. Я відчуваю, що changeBackground буде першим у черзі, а змінити колір буде наступним. Після цього цикл подій вибирає методи синхронно. Маю рацію?
SheshPai

1
@SheshPai це занадто заплутано для всіх, щоб обговорити код, коли вони написані в абзацах англійською мовою. Просто опублікуйте нове запитання з фрагментом коду, щоб люди могли відповісти на основі коду замість опису коду, що залишає багато неоднозначностей.
Пітер Ліонс

youtube.com/watch?v=QyUFheng6J0&spfreload=5 - це ще одне добре пояснення JavaScript Engine
Mukesh Kumar

65

Існує фантастичний відеоурок Філіпа Робертса, який пояснює цикл подій javascript найпростішим та концептуальним способом. Кожен розробник javascript повинен мати вигляд.

Ось відео посилання на Youtube.


16
Я спостерігав за цим, і це було справді найкраще пояснення.
Тарік

1
Потрібно переглянути відео для шанувальників та ентузіастів JavaScript.
Нірус

1
це відео змінює моє живе ^^
HuyTran

1
прийшов сюди блукати .. і це одне з найкращих пояснень, які я отримав .. дякую за обмін ...: D
RohitS

1
Це був окуляр
HebleV

11

Не думайте, що хост-процес є однопоточним, це не так. Однопоточна - це частина хост-процесу, який виконує ваш код JavaScript.

За винятком фонових працівників , але це ускладнює сценарій ...

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

Код 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), а інший протягом секунди (функція inAhile).

Тепер хост-процес спробуйте співставити всі події, щоб побачити, чи є для них зареєстровані обробники. Він виявив, що подія, на яку просто чекає зараз, сталася, тому вона почала запускати свій код. Коли функція justNow виходить, вона інший раз перевіряє цикл подій, шукаючи обробників подій. Припустимо, що пройшло 1 с, він виконує функцію inAhile і так далі ....


Хоча setTimeout реалізований у головному потоці. Тож у вашому прикладі немає нічого, що вимагало б окремої нитки. Насправді в браузерах реалізовані лише вкладки в декількох потоках. На одній вкладці всі процеси, включаючи встановлення декількох паралельних мережевих з'єднань, очікування натискань миші, setTimeout, анімації тощо, виконуються в одній темі
slebetman
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.