В этой статье используется лицензионное соглашение «Signature 4.0 International (CC BY 4.0)», приветствуется перепечатка или изменение для использования, но источник должен быть указан.Атрибуция 4.0 Международная (CC BY 4.0)
Восприятие: 🌟🌟🌟🌟🌟
Вкус: Французская фуа-гра
Время приготовления: 20мин
цикл событий
Последовательность выполнения цикла событий можно увидеть на рисунке.Каждый цикл событий содержит этапы 6 на рисунке выше.Далее давайте интерпретируем их один за другим.
таймеры таймеры
Таймеры делятся на две категории:
- Немедленное выполняется на следующем этапе проверки
- Тайм-аут выполняется после истечения таймера (значение параметра задержки по умолчанию равно 1 мс)
Существует два типа таймеров тайм-аута:
- Interval
- Timeout
这个阶段会执行setTimeout()和setInterval()设定的回调
timers的执行是由poll阶段控制的
setTimeout() и setInterval() такие же, как API в браузере. Принцип их реализации аналогичен асинхронному вводу-выводу, но не требует участия пула потоков ввода-вывода.
После того, как эти два таймера будут созданы, они будут вставлены в красно-черное дерево внутри обозревателя таймеров. Каждый раз, когда выполняется Tick, объекты таймера вынимаются из красно-черного дерева, чтобы проверить, не превышают ли они время отсчета, и их обратные вызовы будут выполняться, если они превышают лимит времени.
Примечание. Проблема с таймерами заключается в том, что они не абсолютно точны (в пределах допуска). Как только задача занимает больше времени в цикле событий, когда наступает очередь таймера для повторного выполнения, это влияет на время.
Нет обработки ввода-вывода
setTimeout(function timeout () {
console.log('timeout');
},0);
setImmediate(function immediate () {
console.log('immediate');
});
Выполнив приведенный выше код, мы можем обнаружить, что результат вывода не определен.
Поскольку setTimeout(fn, 0) имеет погрешность в несколько миллисекунд, нет гарантии, что, войдя в фазу таймера, таймер сможет немедленно выполнить обработчик.
Есть обработка ввода-вывода
var fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
})
// immediate
// timeout
В это время setImmediate имеет приоритет над setTimeout, потому что после завершения фазы опроса она переходит в фазу проверки, а фаза таймеров находится в следующей фазе цикла событий.
ожидающие обратные вызовы ожидающие обратные вызовы
执行部分回调,除了close,times和setImmediate()设定的回调
会在下一次loop中执行的系统级回调队列,如TCP的错误捕获等
праздный, готовься
仅供内部使用
опрос
获取新的I/O事件,在适当的条件下,Node.js会在这里阻塞
这个阶段的主要任务是执行到达delay时间的timers定时器的回调,并且处理poll队列里的事件。
Когда цикл событий переходит в фазу опроса, а таймер не вызывается, происходят две вещи:
1. Если очередь опроса не пуста, цикл событий будет проходить по очереди обратного вызова, выполняя их синхронно.
2. Если очередь опроса пуста, возможны два случая:
-
При вызове обратным вызовом setImmediate() цикл событий завершит фазу опроса и перейдет в фазу проверки.
-
Если обратный вызов setImmediate() не вызывается, цикл обработки событий будет заблокирован и будет ожидать добавления обратного вызова в очередь опроса для выполнения.
Как только очередь опроса станет пустой, цикл событий проверит, достигли ли таймеры времени задержки, и если один или несколько таймеров достигли времени задержки, цикл событий вернется к стадии таймеров таймеров, выполняя их обратные вызовы. .
проверить обнаружение
setImmediate()设定的回调会在这一阶段执行
Как и во втором случае фазы опроса выше, если очередь опроса пуста и вызвана обратным вызовом setImmediate(), цикл обработки событий перейдет непосредственно к фазе проверки.
закрыть обратные вызовы Закрыть функцию обратного вызова
socket.on('close',callback)的回调会在这个阶段执行
libuv
libuv为Node.js提供了整个事件循环功能。
Как показано выше, в Windows цикл событий основан наIOCP
Создать, пройти под linuxepoll
Реализовано, перешло под FreeBSDkqueue
Реализация под Solaris черезEvent ports
выполнить.
Давайте внимательнее посмотрим на картинку выше, методы реализации сетевого ввода-вывода, файлового ввода-вывода, DNS и т. д. разделены, потому что их суть реализуется двумя наборами механизмов. Давайте через мгновение заглянем в их суть через исходный код.
По сути, когда мы пишем код JavaScript для вызова основного модуля Node, основной модуль будет вызывать встроенный модуль C++, а встроенный модуль будет выполнять системные вызовы через libuv.
Основная проблема, которую решает libuv
В реальном мире очень сложно поддерживать разные типы ввода-вывода на разных типах платформ ОС. Затем, чтобы поддерживать межплатформенный ввод-вывод и лучше управлять всем процессом, libuv абстрагируется.
Проще говоря, libuv абстрагирует слой API, который может помочь вам вызывать различные системные функции на разных платформах и машинах, включая рабочие файлы, мониторинг сокетов и т. д., и вам не нужно знать их конкретные реализации.
Интерпретация основного исходного кода
Основная функция uv_run
int uv_run(uv_loop_t* loop, uv_run_mode mode) {
int timeout;
int r;
int ran_pending;
// 检查loop中是否有异步任务,没有就结束。
r = uv__loop_alive(loop);
if (!r)
uv__update_time(loop);
// 事件循环while
while (r != 0 && loop->stop_flag == 0) {
// 更新事件阶段
uv__update_time(loop);
// 处理timer回调
uv__run_timers(loop);
// 处理异步任务回调
ran_pending = uv__run_pending(loop);
// 供内部使用
uv__run_idle(loop);
uv__run_prepare(loop);
// uv_backend_timeout计算完毕后,会传给uv__io_poll
// 如果timeout = 0,则uv__io_poll会直接跳过
timeout = 0;
if ((mode == UV_RUN_ONCE && !ran_pending || mode == UV_RUN_DEFAULT))
timeout = uv_backend_timeout(loop);
uv__io_poll(loop, timeout);
// check阶段
uv__run_check(loop);
// 关闭文件描述符等操作
uv__run_closing_handles(loop);
// 检查loop中是否有异步任务,没有就结束。
r = uv__loop_alive(loop);
if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
break;
}
return r;
}
Настоящее лицо цикла событий — это время.
Как упоминалось выше, сетевой ввод-вывод, файловый ввод-вывод, DNS и т. д. реализуются двумя механизмами.
Во-первых, давайте посмотрим на сетевой ввод-вывод, и его последние вызовы сведутся кuv__io_start
Эта функция, которая помещает события ввода-вывода и обратные вызовы, которые необходимо выполнить, в очередь наблюдателя, иuv__io_poll
Этап возьмет интерфейс системы вызова событий из очереди наблюдателя и выполнит его.
(uv__io_poll
Часть кода слишком длинная, если интересно, можете проверить сами)
uv__io_start
void uv__io_start(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
assert(0 == (events & ~(POLLIN | POLLOUT | UV__POLLRDHUP | UV__POLLPRI)));
assert(0 != events);
assert(w->fd >= 0);
assert(w->fd < INT_MAX);
w->pevents |= events;
maybe_resize(loop, w->fd + 1);
if (w->events == w->pevents)
return;
if (QUEUE_EMPTY(&w->watcher_queue))
QUEUE_INSERT_TAIL(&loop->watcher_queue, &w->watcher_queue);
if (loop->watchers[w->fd] == NULL) {
loop->watchers[w->fd] = w;
loop->nfds++;
}
}
Как показано в процессе реализации основной линии сетевого ввода-вывода libuv.
Другая основная линия заключается в том, что такие операции, как Fs I/O и DNS, будут вызыватьuv__work_sumit
Эта функция, эта функция предназначена для выполнения инициализации пула потоков.uv_queue_work
Функция, которая в итоге вызывается в .
void uv__work_submit(uv_loop_t* loop,
struct uv__work* w,
enum uv__work_kind kind,
void (*work)(struct uv__work* w),
void (*done)(struct uv__work* w, int status)) {
uv_once(&once, init_once);
w->loop = loop;
w->work = work;
w->done = done;
post(&w->wq, kind);
}
int uv_queue_work(uv_loop_t* loop,
uv_work_t* req,
uv_work_cb work_cb,
uv_after_work_cb after_work_cb) {
if (work_cb == NULL)
return UV_EINVAL;
uv__req_init(loop, req, UV_WORK);
req->loop = loop;
req->work_cb = work_cb;
req->after_work_cb = after_work_cb;
uv__work_submit(loop,
&req->work_req,
UV__WORK_CPU,
uv__queue_work,
uv__queue_done);
return 0;
}
Очередь событий в Node.js
В Node.js есть несколько очередей, и разные типы событий помещаются в соответствующие очереди. После завершения одного этапа цикл событий будет обрабатывать промежуточную очередь в середине, прежде чем перейти к следующему этапу.
В родном цикле событий libuv есть четыре основных типа очередей:
-
Истекшие таймеры и интервальные очереди
-
очередь событий ввода-вывода
-
Немедленная очередь
-
закрыть очередь обработчиков
Кроме того, в Node.js есть две промежуточные очереди.
-
Следующая очередь тиков
-
Другая очередь микрозадач
Различия между Node.js и циклом событий браузера
Мы можем просмотреть цикл событий JavaScript в браузере, пожалуйста, посетите мои другие серии столбцовСерия "Attack Front-End Engineer" - Цикл обработки событий JavaScript в браузере
Вернувшись, давайте сначала поговорим о заключении:
В браузере очередь задач микрозадачи выполняется после выполнения каждой макрозадачи.
В Node.js микрозадача будет выполняться между различными этапами цикла событий, то есть после выполнения этапа будут выполняться задачи очереди микрозадач.
(Макрозадача в этой статье называется задачей в WHATWG. Макротаска не имеет фактического источника для простоты понимания.)
По сравнению с браузерами, node имеет большеsetImmediate(宏任务)
а такжеprocess.nextTick(微任务)
Оба являются асинхронными операциями.
setImmediate
Функция обратного вызова находится вcheck
сценическое исполнение. а такжеprocess.nextTick
будет рассматриваться какmicrotask
, после каждого этапа будут выполняться всеmicrotask
, вы можете понимать это какprocess.nextTick
Могурезать по линии, выполненный перед следующим этапом.
Опасности перехода process.nextTick из очереди
Обратный вызов process.nextTick предотвратит переход цикла событий к следующему этапу. После того, как обработка ввода-вывода завершена или таймер истек, ее все еще нельзя выполнить. приведет к голоданию других обработчиков событий. Чтобы предотвратить это, Node.js предоставляетprocess.maxTickDepth
(по умолчанию 1000).
Микрозадачи в Node.js
- process.nextTick()
- Promise.then()
Promise.resolve().then(function(){
console.log('then')
})
process.nextTick(function(){
console.log('nextTick')
});
// nextTick
// then
Мы видим, что nextTick выполняется до этого.
Цикл событий для изменений Node.js v11
Начиная с Node.js v11 принцип цикла событий изменился: пока макрозадача выполняется на одном и том же этапе, очередь микрозадач будет выполняться немедленно, что согласуется с производительностью браузера. Пожалуйста, обратитесь к этомуpr.
❤️ После прочтения трех вещей
1. Когда увидишь это, ставь лайк и поддержи, твой лайк - движущая сила моего творчества.
2. Подпишитесь на официальный аккаунт前端食堂
, ваша передовая столовая, не забывайте есть вовремя!
3. Сейчас зима, не простудитесь, надевая больше одежды~!