Вы действительно понимаете цикл событий?

Node.js внешний интерфейс V8

JS однопоточный

Самой большой особенностью языка JavaScript является однопоточность, но однопоточность здесь относится к тому, что основной поток является однопоточным. Так почему js однопоточный? Поскольку JS в основном используется для управления DOM, если есть два потока, один добавляет контент в DOM, а другой удаляет контент из DOM, какой из них должен выбрать браузер? Поэтому, чтобы избежать сложности, JavaScript с самого начала был однопоточным.

синхронный и асинхронный

Синхронный и асинхронный фокус на механизме уведомления о сообщениях

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

очередь задач

  • потому чтоJavaScriptявляется однониточным. Это означает, что все задачи должны быть поставлены в очередь, и предыдущая задача завершается до того, как следующая задача может быть выполнена. Первая задача занимает много времени, а вторая задача должна ждать все время. Но устройства ввода-вывода (например,ajaxСетевой запрос) очень медленный, а ЦП находится в состоянии первого дня, что очень неразумно.
  • Поэтому фактически основной поток может полностью игнорировать IO-устройство, приостановить ожидающую задачу и сначала запустить следующую задачу. Дождитесь, пока устройство ввода-вывода вернет результат, затем вернитесь и продолжите выполнение приостановленной задачи. Так что естьСинхронизировать задачуа такжеасинхронная задача.

同步任务Это относится к задаче, выполняемой в основном потоке.Только после выполнения предыдущей задачи может быть выполнена следующая задача.异步任务Это означает не вход в основной поток, а вход任务队列(task queue)задача, выполняется только основная задача потока,очередь задачЗадача войдет в основной поток для выполнения.

Цикл событий в браузере

event loop

Смотрите на картинке выше:

  1. При запуске основного потока генерируются куча и стек
  2. Код в стеке вызывает различные внешние API, которые добавляют различные события (щелчок, загрузка, выполнение) в «очередь задач».
  3. Пока код выполняется в стеке, он будет основным потоком для чтения «Очереди задач», очереди событий в стек выполнения в последовательности.
  4. Основной поток продолжает выполняться, и когда внешний API вызывается снова, он добавляется в очередь задач.Когда основной поток завершит выполнение, он поместит события из очереди задач в основной поток.
  5. Весь описанный выше процесс цикличен.

Цикл событий узла

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

node的event loop

Согласно приведенному выше рисунку, механизм работы Node.js выглядит следующим образом:

  1. Написанный сценарий JavaScript будет передан движку V8 для анализа.
  2. После разбора кода вызовите Node API, и Node передаст егобиблиотека libuvиметь дело с
  3. библиотека libuvНазначайте разные задачи разным потокам для формирования цикла событий и возвращайте результаты выполнения задач в механизм V8 асинхронным образом.
  4. Затем движок V8 возвращает результат пользователю.

КромеsetTimeoutа такжеsetIntervalЭти два метода Node.js также предоставляет два других метода, связанных с «очередью задач»:process.nextTickа такжеsetImmediate.

process.nextTickМетод может вызвать функцию обратного вызова в конце текущего «стека выполнения» — перед следующим циклом событий (основной поток считывает «очередь задач»). То есть указанная задача всегда выполняется перед всеми асинхронными задачами.setImmediateМетод заключается в добавлении события в конец текущей «очереди задач», то есть указанная им задача всегда выполняется в следующем цикле событий, что аналогичноsetTimeout(fn, 0)Так же, как.

Английский оригинал: When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.

Цикл событий инициализируется при запуске Node.js, и каждый цикл событий будет содержать шесть этапов цикла в следующем порядке.

   ┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<─────┤  connections, │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘
  • этап таймеров:На этом этапе выполняется запланированный обратный вызов setTimeout (обратный вызов) и setInterval (обратный вызов);
  • Этап обратных вызовов ввода/вывода:Выполнять обратные вызовы, отличные от обратных вызовов события закрытия, обратных вызовов, установленных таймерами (timers, setTimeout, setInterval и т. д.), и обратных вызовов, установленных setImmediate();
  • бездействует, стадия подготовки:Используется только внутри узла;
  • этап опроса:Получить новые события ввода-вывода, узел будет заблокирован здесь при соответствующих условиях;
  • этап проверки:Выполнить обратные вызовы, установленные setImmediate();
  • этап закрытия обратных вызовов:Например, на этом этапе будет выполнен обратный вызов socket.on('close', callback).

Каждый этап имеет очередь fifo (очередь), оснащенную обратными вызовами.Когда цикл событий доходит до указанного этапа, узел будет выполнять очередь fifo (очередь) этого этапа.Когда выполняется обратный вызов очереди или количество выполненных обратных вызовов превышает этап Когда будет достигнут верхний предел, цикл обработки событий перейдет к следующему этапу. **Обратите внимание, что ни один из шести вышеперечисленных этапов не включает process.nextTick(). **process.nextTick() не выполняется ни на одном этапе цикла обработки событий, а выполняется в середине каждого переключения этапов, то есть перед переключением с одного этапа на следующий.

Макрозадачи и микрозадачи

Задачи можно разделить на макрозадачи и микрозадачи

Общие макрозадачи и микрозадачи:

  1. macro-task(宏任务): setTimeout, setInterval, setImmediate, I/O
  2. micro-task(微任务):process.nextTick, РоднойPromise(некоторые реализованыpromiseБудуthenметод помещается в задачу макроса),Object.observe(устаревший),MutationObserver

См. пример ниже:

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

Каков порядок выполнения приведенного выше кода? Почему это?

script start
script end
promise1
promise2
setTimeout

TaskОн отправляется и выполняется в строгом хронологическом порядке, чтобы браузер могJavaScriptвнутренние задачи иDOMЗадачи могут выполняться по порядку. Когда задача завершает выполнение, следующаяtaskБраузер может повторно отобразить страницу перед началом выполнения. КаждыйtaskДолжны быть назначены, например, от кликов пользователя к событию клика, рендеринга HTML-документов, а также в приведенном выше примереsetTimeout.

На основании ранее описанногоevent loop,setTimeoutон выделит новый по истечении времени задержкиtaskкevent loopвместо немедленного выполнения, поэтомуsetTimeoutФункция обратного вызова будет ждать завершения выполнения предыдущих задач перед запуском. поэтомуsetTimeoutБудет выводитьscript endПосле этого, посколькуscript endявляется частью первой задачи, иsetTimeoutэто новыйtask.

微任务Обычно это требуетсяЗадача, которая будет выполняться сразу после завершения выполнения текущей задачиНапример, вам нужно ответить на ряд задач или вам нужно выполнять задачи асинхронно, не назначая новую задачу, что может снизить нагрузку на производительность.

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

   всякий раз, когдаPromiseЕсли она разрешена (или отклонена), ее функция обратного вызова будет добавлена ​​в очередь микрозадач как новая микрозадача. Это также гарантируетPromiseВы можете выполнить асинхронный. Итак, когда мы звоним.then(resolve, reject), новая микрозадача будет сгенерирована немедленно и добавлена ​​в очередь, поэтому приведенное вышеpromise1а такжеpromise2будет выводиться вscript endПосле этого, поскольку задачи в очереди микрозадач должны ждать текущегоtaskВыполнить после выполнения иpromise1а такжеpromise2вывод вsetTimeoutРаньше это было потому, чтоsetTimeoutэто новыйtask, а микрозадача выполняется в текущемtaskПосле окончания следующийtaskдо начала.

Ссылаться на:

  1. The Node.js Event Loop, Timers, and process.nextTick()
  2. Подробное объяснение механизма работы JavaScript: снова поговорим о цикле событий
  3. The Node.js Event Loop, Timers, and process.nextTick()
  4. Глубокое понимание цикла событий JavaScript (2) — задача и микрозадача