翼度科技»论坛 编程开发 JavaScript 查看内容

[NodeJS] timers阶段的源码解析

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
timers阶段是Nodejs事件循环中的一个阶段,这一阶段主要是检查是否有到期的定时器,如果有则执行其回调。
相关源码位置:
timers阶段的代码比较少,这里直接贴出来,你也可以点进去上面的源码看自己感兴趣的部分:
  1. void uv__run_timers(uv_loop_t* loop) {
  2.   struct heap_node* heap_node;
  3.   uv_timer_t* handle;
  4.   struct uv__queue* queue_node;
  5.   struct uv__queue ready_queue;
  6.   
  7.   // 初始化一个空的 ready_queue 队列,用于存放已经到期的定时器
  8.   uv__queue_init(&ready_queue);
  9.   for (;;) {
  10.     // 获取堆中最小(即最早到期)的定时器节点
  11.     heap_node = heap_min(timer_heap(loop));
  12.     if (heap_node == NULL)
  13.       break;  // 如果堆是空的(没有定时器),则跳出循环
  14.     // 将堆节点转换为 uv_timer_t 类型的定时器句柄 handle
  15.     handle = container_of(heap_node, uv_timer_t, node.heap);
  16.     if (handle->timeout > loop->time)
  17.       break;  // 如果当前定时器的超时时间大于当前的循环时间,则跳出循环
  18.     // 停止到期的定时器,并将其插入到 ready_queue 队列的尾部
  19.     uv_timer_stop(handle);
  20.     uv__queue_insert_tail(&ready_queue, &handle->node.queue);
  21.   }
  22.   
  23.   // 处理 ready_queue 中的所有定时器
  24.   while (!uv__queue_empty(&ready_queue)) {
  25.     queue_node = uv__queue_head(&ready_queue);  // 取出队列头部的节点
  26.     uv__queue_remove(queue_node);  // 移除该节点
  27.     uv__queue_init(queue_node);  // 重新初始化节点
  28.     handle = container_of(queue_node, uv_timer_t, node.queue);  // 将节点转换为定时器句柄 handle
  29.     // 重新启动定时器,如果是重复定时器,则根据设定的间隔重新计算超时时间
  30.     uv_timer_again(handle);
  31.     // 调用定时器的回调函数,执行定时器到期后的操作
  32.     handle->timer_cb(handle);
  33.   }
  34. }
复制代码
内容比较少,这里也直接贴出来:
  1. int uv_timer_again(uv_timer_t* handle) {
  2.   if (handle->timer_cb == NULL)
  3.     return UV_EINVAL;
  4.   if (handle->repeat) {
  5.     uv_timer_stop(handle);
  6.     uv_timer_start(handle, handle->timer_cb, handle->repeat, handle->repeat);
  7.   }
  8.   return 0;
  9. }
复制代码
可以看到会检查handle->repeat,这里其实就是setTimeout和setInterval的区别了,setInterval到这里会因为handle->repeat为true而重新开启新的一轮计时,而setTimeout则是直接跳过、结束了。
这里的handle->repeat是uint64_t类型,其实就是timeout。
这里顺便贴一下uv_timer_start的代码,感兴趣可以看看。
源码位置:node/deps/uv/src/timer.c at main · nodejs/node (github.com)
  1. int uv_timer_start(uv_timer_t* handle,
  2.                    uv_timer_cb cb,
  3.                    uint64_t timeout,
  4.                    uint64_t repeat) {
  5.   uint64_t clamped_timeout;
  6.   
  7.   // 如果定时器正在关闭或回调函数为 NULL,则返回错误代码 UV_EINVAL
  8.   if (uv__is_closing(handle) || cb == NULL)
  9.     return UV_EINVAL;
  10.   
  11.   // 停止定时器,以确保定时器在重新启动前没有在运行
  12.   uv_timer_stop(handle);
  13.   
  14.   // 计算定时器的超时时间 clamped_timeout
  15.   // 它是当前事件循环时间 handle->loop->time 加上 timeout
  16.   clamped_timeout = handle->loop->time + timeout;
  17.   // 如果发生整数溢出,导致 clamped_timeout 小于 timeout,
  18.   // 则将 clamped_timeout 设置为最大值 UINT64_MAX
  19.   if (clamped_timeout < timeout)
  20.     clamped_timeout = (uint64_t) -1;
  21.   // 设置定时器的回调函数、超时时间和重复间隔
  22.   handle->timer_cb = cb;
  23.   handle->timeout = clamped_timeout;
  24.   handle->repeat = repeat;
  25.   
  26.   // start_id 用于在定时器比较函数 timer_less_than 中作为第二索引进行比较
  27.   // 并递增事件循环的计时器计数器
  28.   handle->start_id = handle->loop->timer_counter++;
  29.   // 将定时器插入到事件循环的定时器堆中,并启动定时器句柄
  30.   heap_insert(timer_heap(handle->loop),
  31.               (struct heap_node*) &handle->node.heap,
  32.               timer_less_than);
  33.   uv__handle_start(handle);
  34.   return 0;
  35. }
复制代码

  • clamped_timeout和timeout之间的关系:

    • timeout 是传递给 uv_timer_start 函数的参数,表示定时器的初始延迟时间,以毫秒为单位;
    • clamped_timeout 是计算后的绝对时间,表示定时器实际超时的绝对时间点(以事件循环的时间 handle->loop->time 为基准)。

  • uv_timer_start的timeoute参数 和 JS中setTimeout的delay参数是等价的。

来源:https://www.cnblogs.com/feixianxing/p/18284332/nodejs-event-loop-timers
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具