Изначально этот пост был написан как ответ на проблемы 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 г.