nodejs之setImmediate源码分析
发布于 4 年前 作者 theanarkh 4543 次浏览 来自 分享

setImmediate函数的代码在lib/timer.js。

function setImmediate(callback, arg1, arg2, arg3) {
  if (typeof callback !== 'function') {
    throw new errors.TypeError('ERR_INVALID_CALLBACK');
  }

  var i, args;
  switch (arguments.length) {
    // fast cases
    case 1:
      break;
    case 2:
      args = [arg1];
      break;
    case 3:
      args = [arg1, arg2];
      break;
    default:
      args = [arg1, arg2, arg3];
      for (i = 4; i < arguments.length; i++) {
        // extend array dynamically, makes .apply run much faster in v6.0.0
        args[i - 1] = arguments[i];
      }
      break;
  }

  return new Immediate(callback, args);
}

const Immediate = class Immediate {
  constructor(callback, args) {
    this._idleNext = null;
    this._idlePrev = null;
    // this must be set to null first to avoid function tracking
    // on the hidden class, revisit in V8 versions after 6.2
    this._onImmediate = null;
    this._onImmediate = callback;
    this._argv = args;
    this._destroyed = false;
    this[kRefed] = false;

    this[async_id_symbol] = ++async_id_fields[kAsyncIdCounter];
    this[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
    if (async_hook_fields[kInit] > 0) {
      emitInit(this[async_id_symbol],
               'Immediate',
               this[trigger_async_id_symbol],
               this);
    }

    this.ref();
    immediateInfo[kCount]++;

    immediateQueue.append(this);
  }
  
function ImmediateList() {
  this.head = null;
  this.tail = null;
}

从上面的代码中我们知道,调用setImmediate函数后,nodejs会把回调和参数存在一个队列里。等待回调。然后处理队列里的每个节点。下面我们看一下处理函数的代码。

const {
  Timer: TimerWrap,
  setImmediateCallback,
} = process.binding('timer_wrap');
const [immediateInfo, toggleImmediateRef] = setImmediateCallback(processImmediate)

function processImmediate() {
  const queue = outstandingQueue.head !== null ?
    outstandingQueue : immediateQueue;
  var immediate = queue.head;
  const tail = queue.tail;

  // Clear the linked list early in case new `setImmediate()` calls occur while
  // immediate callbacks are executed
  queue.head = queue.tail = null;

  let count = 0;
  let refCount = 0;

  while (immediate !== null) {
    immediate._destroyed = true;

    const asyncId = immediate[async_id_symbol];
    emitBefore(asyncId, immediate[trigger_async_id_symbol]);

    count++;
    if (immediate[kRefed])
      refCount++;
    immediate[kRefed] = undefined;

    tryOnImmediate(immediate, tail, count, refCount);

    emitAfter(asyncId);

    immediate = immediate._idleNext;
  }

  immediateInfo[kCount] -= count;
  immediateInfo[kRefCount] -= refCount;
  immediateInfo[kHasOutstanding] = 0;
}

setImmediateCallback函数来自timer_wrapper.cc,我们看到nodejs执行了该函数,并且以processImmediate作为入参。所以我们来看看这个函数的代码。

static void SetImmediateCallback(const FunctionCallbackInfo<Value>& args) {
    CHECK(args[0]->IsFunction());
    auto env = Environment::GetCurrent(args);
    // 保存回调函数
    env->set_immediate_callback_function(args[0].As<Function>());
    auto toggle_ref_cb = [] (const FunctionCallbackInfo<Value>& args) {
      Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue());
    };
    auto toggle_ref_function =
        env->NewFunctionTemplate(toggle_ref_cb)->GetFunction(env->context())
        .ToLocalChecked();
    auto result = Array::New(env->isolate(), 2);
    result->Set(env->context(), 0,
                env->immediate_info()->fields().GetJSArray()).FromJust();
    result->Set(env->context(), 1, toggle_ref_function).FromJust();
    args.GetReturnValue().Set(result);
  }

该函数就是在env里保存了回调函数的信息。set_immediate_callback_function函数是在env里用宏定义的,执行的时候对应的函数是env->immediate_callback_function(),该函数在env.cc的CheckImmediate中执行,那么CheckImmediate又在什么时候被执行呢?我们接着看。原来在 Environment::Start的时候注册了该函数。

  uv_check_start(immediate_check_handle(), CheckImmediate)

uv_check_start是libuv的函数。

  int uv_##name##_start(uv_##name##_t* handle, uv_##name##_cb cb) {           \

   // 如果已经执行过start函数则直接返回
    if (uv__is_active(handle)) return 0;                                      \
    if (cb == NULL) return UV_EINVAL;                                         \

    // 把handle插入loop中相应类型的队列,loop有prepare,idle和check三个队列
    QUEUE_INSERT_HEAD(&handle->loop->name##_handles, &handle->queue);         \

    // 挂载回调,下一轮循环的时候被执行
    handle->name##_cb = cb;                                                   \

   // 设置UV_HANDLE_ACTIVE标记位,并且loop中的handle数加一,init的时候只是把handle挂载到loop,start的时候handle才处于激活态
    uv__handle_start(handle);                                                 \
    return 0;                                                                 \
  }       

我们从代码里可以看到,就把把一个handle插到了loop的队列里。然后在uv_run的循环中执行下面的函数。

uv__run_check(loop);

// 在每一轮循环中执行该函数,执行时机见uv_run
  void uv__run_##name(uv_loop_t* loop) {                                      \
    uv_##name##_t* h;                                                         \
    QUEUE queue;                                                              \
    QUEUE* q;                                                                 \

    // 把该类型对应的队列中所有节点摘下来挂载到queue变量
    QUEUE_MOVE(&loop->name##_handles, &queue);                                \

   // 遍历队列,执行每个节点里面的函数
    while (!QUEUE_EMPTY(&queue)) {                                            \

      // 取下当前待处理的节点
      q = QUEUE_HEAD(&queue);                                                 \

      // 取得该节点对应的整个结构体的基地址
      h = QUEUE_DATA(q, uv_##name##_t, queue);                                \

      // 把该节点移出当前队列
      QUEUE_REMOVE(q);                                                        \

     // 重新插入原来的队列
      QUEUE_INSERT_TAIL(&loop->name##_handles, q);                            \

     // 执行回调函数
      h->name##_cb(h);                                                        \
    }                                                                         \
  }     

然后就会执行刚才注册的CheckImmediate,一直执行到nodejs的processImmediate函数。所以setImmediate的执行时机是在uv__run_check这个阶段。另外提一下的就是setImmediate和setTimeout谁先谁后的问题。这个其实是不一定的。从uv_run中我们看到执行定时器的代码比是比uv__run_check先的,但是如果我们在执行完定时器之后,uv__run_check之前,又新增了一个定时器和执行了setImmediate,那么setImmediate的回调就会先执行。

回到顶部