浅析libuv源码-node事件轮询解析(1)

  很久没写东西了,忙得要死,前几天忽然想起了脉脉上面一句话: 要时刻保持本身的竞争力。因此,继续开写!
  通常的JavaScript源码看的已经没啥意思了,我也不会写什么xx入门新手教程,最终决定仍是啃原来的硬骨头,从外层libuv => node => v8一步步实现原有的目标吧。
  libuv核心仍是事件轮询,前几天从头至尾看了一遍官网的文档,对此有了一些更深的理解。
  (虽然如今开发用的mac,可是为了衔接前面的文章,因此代码仍旧以windows系统为基础,反正差异也不大)
  首先看一眼官网给的图:


  理论上轮询都是一个无尽循环,因此不用在乎loop alive问题。
  上图中,关于udpate loop time、Run due timers两块内容我已经在别的博客中讲解过,这里就懒得发传送门了。
  有两个简单的概念须要稍微提一下,libuv中有两个抽象概念贯穿整个框架:handle、request。其中handle生命周期较长,且有本身回调方法的一个事务,好比说TCP的handle会处理每个TCP链接,并触发connection事件。request属于handle中一个生命周期短,且简单的行为,好比向文件进行读、写等等。
  这一篇主要看一下接下来剩余的部分,因为性质不太同样,因此并不会按顺序依次分析,而是从易到难,且源码会作大量简化,有兴趣的人能够本身去看原生态的。
  事件轮询方法源码精炼以下:
int uv_run(uv_loop_t *loop, uv_run_mode mode) {
  // ...

  while (r != 0 && loop->stop_flag == 0) {
    // update loop time
    uv_update_time(loop);
    // run due timers
    uv__run_timers(loop);
    // call pending callbacks
    ran_pending = uv_process_reqs(loop);
    // run idle handles
    uv_idle_invoke(loop);
    // run prepare handles
    uv_prepare_invoke(loop);
    // poll的阻塞时间处理
    timeout = 0;
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
      timeout = uv_backend_timeout(loop);
    // poll for I/O
    if (pGetQueuedCompletionStatusEx)
      uv__poll(loop, timeout);
    else
      uv__poll_wine(loop, timeout);

    // run check handles
    uv_check_invoke(loop);
    // call close callbacks
    uv_process_endgames(loop);
  }
  // ...
  return r;
}复制代码

Call close callbackshtml

  这类回调比较特殊,官网是这么解释的: Close callbacks are called. If a handle was closed by calling uv_close() it will get the close callback called.
  简单来说,就是仅在为了关闭一个handle,调用uv_close方法中所带的callback会被认为是一个close callbacks。在使用node的时候,全部的操做(好比fs.readFile)不可主动取消,因此轮询中这一步在JS层面是感知不到的。
  做用上至关于vue钩子函数中的destroy,因为触发是在轮询的最后一步,适合作一些收尾的工做,好比关闭文件描述符等等。
  源码中体现以下,首先是uv_close:
void uv_close(uv_handle_t* handle, uv_close_cb cb) {
    // 不少代码...
 
    case UV_PREPARE:
      uv_prepare_stop((uv_prepare_t*)handle);
      uv__handle_closing(handle);
      uv_want_endgame(loop, handle);
      return;
}复制代码
  uv_close方法除了作关闭handle的本职工做,在最后都会调用一个uv_want_endgame方法收尾,这个方法是一个静态方法。
INLINE static void uv_want_endgame(uv_loop_t* loop, uv_handle_t* handle) {
  if (!(handle->flags & UV_HANDLE_ENDGAME_QUEUED)) {
    handle->flags |= UV_HANDLE_ENDGAME_QUEUED;

    handle->endgame_next = loop->endgame_handles;
    loop->endgame_handles = handle;
  }
}复制代码
  内容十分简单,将handle插入到endgame_handles这个链表的表头。
  最后,只须要看一眼uv_process_endgames便可。
INLINE static void uv_process_endgames(uv_loop_t* loop) {
  uv_handle_t* handle;

  while (loop->endgame_handles) {
    handle = loop->endgame_handles;
    loop->endgame_handles = handle->endgame_next;

    handle->flags &= ~UV_HANDLE_ENDGAME_QUEUED;

    switch (handle->type) {
      case UV_TCP:
        uv_tcp_endgame(loop, (uv_tcp_t*) handle);
        break;
      // ...
    }
  }
}复制代码
  也很简洁明了,不停的取出endgame_handles链表中的handle,依次调用不一样的callbacks便可。


Run idle hanldes、Run prepare handles、Run check handles
  这三个虽然名字不同,可是主要做用相似,只是在调用顺序上有所不一样。
  因为Poll for I/O是一个比较特殊的操做,因此这里提供prepare、check两个钩子函数能够在这个事务先后进行一些别的调用,大能够用vue的钩子函数created、mounted来帮助理解。
  idle除去调用较早,也影响poll for I/O这个操做的阻塞时间timeout,官网原文: If there are any idle handles active, the timeout is 0.正常状况下事件轮询会根据状况计算一个阻塞时间timout来决定poll for I/O操做的时间。
  这里用一个C++例子来证实调用顺序,忽略上面的宏,直接看main函数,特别简单!!!
#include <iostream>
#include "uv.h"
using namespace std;

void idle_callback(uv_idle_t* idle);
void prepare_callback(uv_prepare_t* prepare);
void check_callback(uv_check_t* check);

#define RUN_HANDLE(type) \ do { \ uv_##type##_t type; \ uv_##type##_init(loop, &type); \ uv_##type##_start(&type, type##_callback); \ } while(0)

#define CALLBACK(type) \ do { \ cout << "Run " << #type << " handles" << endl; \ uv_##type##_stop(type); \ } while(0)

#define OPEN(PATH, callback) \ do { \ uv_fs_t req; \ uv_fs_open(loop, &req, PATH, O_RDONLY, 0, callback); \ uv_fs_req_cleanup(&req); \ } while(0)

void idle_callback(uv_idle_t* idle) { CALLBACK(idle); }
void prepare_callback(uv_prepare_t* prepare) { CALLBACK(prepare); }
void check_callback(uv_check_t* check) { CALLBACK(check); }
void on_open(uv_fs_t* req) { cout << "poll for I/O" << endl; }

int main(int argc, const char * argv[]) {
    auto loop = uv_default_loop();
    
    RUN_HANDLE(check);
    RUN_HANDLE(prepare);
    RUN_HANDLE(idle);
    
    OPEN("/Users/feilongpang/workspace/i.js", on_open);
    
    uv_run(loop, UV_RUN_DEFAULT);
    uv_loop_close(loop);
    return 0;
}复制代码
  执行的时候还发现了一个问题,若是不提供一个I/O操做,Run check handles那一步是会直接跳过,因此手动加了一个open操做。
  能够看到,我特地调整了callback的添加顺序,可是输出依然是:
  因此,代码确实是按照官网示例所给的图顺序来执行。
  剩下两个poll for I/O、pending callbacks留到下一篇讲吧。
相关文章
相关标签/搜索