[Day 23] Node Event loop 2

前言

今天继续看看 event loop 的核心循环, uv_run() , 可以查看以下网址

https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/deps/uv/src/win/core.c

主要循环

int uv_run(uv_loop_t *loop, uv_run_mode mode) {
  DWORD 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_process_reqs(loop);
    uv_idle_invoke(loop);
    uv_prepare_invoke(loop);

    timeout = 0;
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
      timeout = uv_backend_timeout(loop);

    if (pGetQueuedCompletionStatusEx)
      uv__poll(loop, timeout);
    else
      uv__poll_wine(loop, timeout);

    /* Run one final update on the provider_idle_time in case uv__poll*
     * returned because the timeout expired, but no events were received. This
     * call will be ignored if the provider_entry_time was either never set (if
     * the timeout == 0) or was already updated b/c an event was received.
     */
    uv__metrics_update_idle_time(loop);

    uv_check_invoke(loop);
    uv_process_endgames(loop);

    if (mode == UV_RUN_ONCE) {
      /* UV_RUN_ONCE implies forward progress: at least one callback must have
       * been invoked when it returns. uv__io_poll() can return without doing
       * I/O (meaning: no callbacks) when its timeout expires - which means we
       * have pending timers that satisfy the forward progress constraint.
       *
       * UV_RUN_NOWAIT makes no guarantees about progress so it's omitted from
       * the check.
       */
      uv__run_timers(loop);
    }

    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }

  /* The if statement lets the compiler compile it to a conditional store.
   * Avoids dirtying a cache line.
   */
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;

  return r;
}

原则上这个循环就是不断的在处理任务, 可以称得上是 node.js 背後默默做事的工具人了。

今天我们先看

uv_update_time(loop);
uv__run_timers(loop);

这两句

uv_update_time

void uv_update_time(uv_loop_t* loop) {
  uint64_t new_time = uv__hrtime(1000);
  assert(new_time >= loop->time);
  loop->time = new_time;
}

主要是利用 uv__hrtime 取得新的时间

static void uv__hrtime_init_once(void) {
  if (KERN_SUCCESS != mach_timebase_info(&timebase))
    abort();

  time_func = (uint64_t (*)(void)) dlsym(RTLD_DEFAULT, "mach_continuous_time");
  if (time_func == NULL)
    time_func = mach_absolute_time;
}

uint64_t uv__hrtime(uv_clocktype_t type) {
  uv_once(&once, uv__hrtime_init_once);
  return time_func() * timebase.numer / timebase.denom;
}

uv__hrtime 主要就是调用一些 OS API 取得当下时间

之所以不每次要时间都和 OS 要, 是为了避免 , system call 会阻塞运作, 所以尽量都维持在 user mode

uv__run_timers

重点环节, 提供注释

void uv__run_timers(uv_loop_t* loop) {
	// 取得 heap 首节点
  struct heap_node* heap_node;
  uv_timer_t* handle;

  for (;;) {
		// 取得 heap 中最小节点
    heap_node = heap_min(timer_heap(loop));
		// heap 为空则断开
    if (heap_node == NULL)
      break;
		// 当 最小节点也大於当前时间, 就退出
    handle = container_of(heap_node, uv_timer_t, heap_node);
    if (handle->timeout > loop->time)
      break;
		// 删除节点
    uv_timer_stop(handle);
		// 如果是重复任务, 就再插回 heap
    uv_timer_again(handle);
		// 执行节点任务
    handle->timer_cb(handle);
  }
}

这句方法主要是用来处理需要定期触发的任务, 可以看到 node 维护了一个 heap ( 最小树 ) , 当有以时间为触发基准的任务被注册後, 就包成节点放入 heap , 节点中包含事件触发时间及其回调函数。

因为最小树的顶端永远是最小节点 (在这里可以视为触发时间最早者) , 所以可以用最快的速度锁定要处理的任务。 以下简述流程。

  1. JS 层注册事件要求 5 秒後触发 callback
  2. heap 被放入节点, 节点值是 5 秒後的时间, 且因为其为 heap 中的最小数, 所以在树顶
  3. 6 秒後 uv_run 运作到 uv__run_timers
  4. 取出树顶, 发现触发时间晚於当前时间, 表示该触发了
  5. 删除节点
  6. 执行节点所包含的 callback

明天进度

明天我们来看看 , uv__io_poll(loop, timeout) ,中间会先跳过一些阶段, 希望藉此可以在看其他阶段前有更宏观的视野。

明天见 !


<<:  Day09:09 - User服务(4) - 前端 - JWT token、修改个人资料

>>:  DAY03 - 到github放置play

Day 3 - 新人报到前的准备与莫名的焦虑感

确定了offer也确定了报到时间後,距离到职日大概还有两周多的时间,因为自己是北漂青年因此开始寻找後...

纯JS实作照片上传、下载与预览

更多会员限定文章可以到patreon观看 完整code可以到以下gist Client端HTML &...

Day14 - 机智接案生活

看过很多文章提到程序设计师接案的陷阱,因自己非本科出身,所以觉得这些陷阱都不会发生在自己身上,再加上...

新增装备 - VSCode 套件介绍

前情提要 身後传来了声音:「哈罗,我叫艾草,是你的入门引导学姊。」 我回头一看却没看到人。 「这里!...

[Day 30] 永和美食纪录-向日葵早午餐 国中店

前言 今天文章的标题完完全全打脸了笔者在 Day27 的结语,没想到在最後一天仍然还是介绍了早午餐给...