Изначально этот пост был написан как ответ на проблемы github с помощью nodejs https://github.com/nodejs/help/issues/392, а позже получил более постоянное место в моем блоге.

Документация Nodejs Цикл событий Node.js, таймеры и process.nextTick хорошо объясняет концепцию цикла событий.

Но в конце документа, объясняя setImmediate () vs setTimeout (), он дал пример кода,

setTimeout(function timeout () {
 console.log(‘timeout’);
},0);
setImmediate(function immediate () {
 console.log(‘immediate’);
});
$ node timeout_vs_immediate.js
timeout
immediate
$ node timeout_vs_immediate.js
immediate
timeout

и ушел с этой строкой

«порядок, в котором выполняются два таймера, недетерминирован, так как он связан с производительностью процесса:».

Это попытка выяснить, что имеется в виду под словом «связанный с производительностью процесса».

Цикл событий и еще раз фазы

Потерпите меня, я не повторяю вещей, которые уже были подробно освещены во многих постах.

Каждое событие в node.js управляется uv_run() функцией libuv. Частичный код

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
 int timeout;
 int r;
 int ran_pending;
 r = uv__loop_alive(loop);
 if (!r)
 uv__update_time(loop);
 while (r != 0 && loop->stop_flag == 0) {
 uv__update_time(loop);
 uv__run_timers(loop);
 ran_pending = uv__run_pending(loop);
 uv__run_idle(loop);
 uv__run_prepare(loop);
 ……
 uv__io_poll(loop, timeout);
 uv__run_check(loop);
 uv__run_closing_handles(loop);
 …………

Итак, как объясняется в документе node.js, мы можем сопоставить каждую фазу цикла событий в коде.

Фаза таймера uv__run_timers(loop);

Обратный вызов ввода-вывода ran_pending = uv__run_pending(loop);

простаивает / готовится uv__run_idle(loop); uv__run_prepare(loop);

опрос uv__io_poll(loop, timeout);

проверьте uv__run_check(loop);

закрыть обратные вызовы uv__run_closing_handles(loop);

Больше чем фаза

Да, в цикле событий есть не только фазы. Если мы внимательно видим код до того, как цикл перейдет в фазу таймера, он вызывает
uv__update_time(loop); для инициализации времени цикла.

void uv_update_time (uv_loop_t * loop) {
uv__update_time (loop);
}

Происходит uv__update_time(loop) вызов функции uv__hrtime

UV_UNUSED(static void uv__update_time(uv_loop_t* loop)) {
 
 */
 loop->time = uv__hrtime(UV_CLOCK_FAST) / 1000000;
}

Этот вызов uv__hrtime зависит от платформы и требует много времени для процессора, поскольку он вызывает системный
вызов clock_gettime. На него влияет другое приложение, работающее на машине.

uint64_t uv__hrtime(uv_clocktype_t type) {
 struct timespec ts;
 clock_gettime(CLOCK_MONOTONIC, &ts);
 return (((uint64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec);
}

Как только это будет возвращено, в цикле событий вызывается фаза таймера.

void uv__run_timers(uv_loop_t* loop) {
 …
 for (;;) {
 ….
 handle = container_of(heap_node, uv_timer_t, heap_node);
 if (handle->timeout > loop->time)
 break;
 ….
 uv_timer_again(handle);
 handle->timer_cb(handle);
 }
}

Обратный вызов на этапе таймера запускается, если текущее время цикла больше тайм-аута.
Еще одна важная вещь, на которую следует обратить внимание: setTimeout, когда установлено значение 0, внутренне преобразуется в 1.
Также как hr_time время возврата в наносекундах, это поведение, показанное timeout_vs_immediate.js, теперь становится более понятным.

Если подготовка перед первым циклом заняла более 1ms, тогда фаза Timer вызывает связанный с ней обратный вызов. Если оно меньше 1ms. Цикл событий переходит к следующей фазе и выполняет setImmediate обратный вызов в фазе проверки цикла и setTimeout в
следующем тике цикла.

Надеюсь, это проясняет способ обхода недетерминированного поведения setTimeout и setImmediate, когда оба вызываются из основного модуля.

Первоначально опубликовано на сайте ankuranand.com 3 июня 2017 г.