nodejs之setTimeout源码解析 - CNode技术社区

nodejs之setTimeout源码解析
发布于 6 年前 作者 theanarkh 6017 次浏览 来自 分享

setTimeout是在系统启动的时候挂载的全局函数。代码在timer.js。

function setupGlobalTimeouts() {
 const timers = NativeModule.require('timers');
 global.clearImmediate = timers.clearImmediate;
 global.clearInterval = timers.clearInterval;
 global.clearTimeout = timers.clearTimeout;
 global.setImmediate = timers.setImmediate;
 global.setInterval = timers.setInterval;
 global.setTimeout = timers.setTimeout;
 }

我们先看一下setTimeout函数的代码。

function setTimeout(callback, after, arg1, arg2, arg3) {
 if (typeof callback !== 'function') {
 throw new errors.TypeError('ERR_INVALID_CALLBACK');
 }
 var i, args;
 switch (arguments.length) {
 // fast cases
 case 1:
 case 2:
 break;
 case 3:
 args = [arg1];
 break;
 case 4:
 args = [arg1, arg2];
 break;
 default:
 args = [arg1, arg2, arg3];
 for (i = 5; i < arguments.length; i++) {
 // extend array dynamically, makes .apply run much faster in v6.0.0
 args[i - 2] = arguments[i];
 }
 break;
 }
 // 新建一个对象,保存回调,超时时间等数据,是超时哈希队列的节点
 const timeout = new Timeout(callback, after, args, false, false);
 // 启动超时器
 active(timeout);
 // 返回一个对象
 return timeout;
}

其中Timeout函数在lib/internal/timer.js里定义。

function Timeout(callback, after, args, isRepeat, isUnrefed) {
 after *= 1; // coalesce to number or NaN
 this._called = false;
 this._idleTimeout = after;
 this._idlePrev = this;
 this._idleNext = this;
 this._idleStart = null;
 this._onTimeout = null;
 this._onTimeout = callback;
 this._timerArgs = args;
 this._repeat = isRepeat ? after : null;
 this._destroyed = false;
 this[unrefedSymbol] = isUnrefed;
 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],
 'Timeout',
 this[trigger_async_id_symbol],
 this);
 }
}

由代码可知,首先创建一个保存相关信息的对象,然后执行active函数。

const active = exports.active = function(item) {
 // 插入一个超时对象到超时队列
 insert(item, false);
}
function insert(item, unrefed, start) {
 // 超时时间
 const msecs = item._idleTimeout;
 if (msecs < 0 || msecs === undefined) return;
 // 如果传了start则计算是否超时时以start为起点,否则取当前的时间
 if (typeof start === 'number') {
 item._idleStart = start;
 } else {
 item._idleStart = TimerWrap.now();
 }
 // 哈希队列
 const lists = unrefed === true ? unrefedLists : refedLists;
 var list = lists[msecs];
 // 没有则新建一个队列
 if (list === undefined) {
 debug('no %d list was found in insert, creating a new one', msecs);
 lists[msecs] = list = new TimersList(msecs, unrefed);
 }
 ...
 // 把超时节点插入超时队列
 L.append(list, item);
 assert(!L.isEmpty(list)); // list is not empty
}

从上面的代码可知,active一个定时器实际上是把新建的timeout对象挂载到一个哈希队列里。我们看一下这时候的内存视图。 在这里插入图片描述 当我们创建一个timerList的是时候,就会关联一个底层的定时器,执行setTimeout时传进来的时间是一样的,都会在一条队列中进行管理,该队列对应一个定时器,当定时器超时的时候,就会在该队列中找出超时节点。下面我们看一下new TimeWraper的时候发生了什么。

 TimerWrap(Environment* env, Local<Object> object) : HandleWrap(env, object,reinterpret_cast<uv_handle_t*>(&handle_),AsyncWrap::PROVIDER_TIMERWRAP) {
 int r = uv_timer_init(env->event_loop(), &handle_);
 CHECK_EQ(r, 0);
 }

其实就是初始化了一个libuv的uv_timer_t结构体。然后接着start函数做了什么操作。

 static void Start(const FunctionCallbackInfo<Value>& args) {TimerWrap* wrap = Unwrap<TimerWrap>(args.Holder());
 CHECK(HandleWrap::IsAlive(wrap));
 int64_t timeout = args[0]->IntegerValue();
 int err = uv_timer_start(&wrap->handle_, OnTimeout, timeout, 0);
 args.GetReturnValue().Set(err);
 }

就是启动了刚才初始化的定时器。并且设置了超时回调函数是OnTimeout。这时候,就等定时器超时,然后执行OnTimeout函数。所以我们继续看该函数的代码。

 const uint32_t kOnTimeout = 0;
 static void OnTimeout(uv_timer_t* handle) {
 TimerWrap* wrap = static_cast<TimerWrap*>(handle->data);
 Environment* env = wrap->env();
 HandleScope handle_scope(env->isolate());
 Context::Scope context_scope(env->context());
 wrap->MakeCallback(kOnTimeout, 0, nullptr);
 }

OnTimeout函数继续调kOnTimeout,但是该变量在time_wrapper.c中是一个整形,这是怎么回事呢?这时候需要回lib/timer.js里找答案。

const kOnTimeout = TimerWrap.kOnTimeout | 0;
// adds listOnTimeout to the C++ object prototype, as
// V8 would not inline it otherwise.
// 在TimerWrap中是0,给TimerWrap对象挂一个超时回调,每次的超时都会执行该回调
TimerWrap.prototype[kOnTimeout] = function listOnTimeout() {
 // 拿到该底层定时器关联的超时队列,看TimersList
 var list = this._list;
 var msecs = list.msecs;
 //
 if (list.nextTick) {
 list.nextTick = false;
 process.nextTick(listOnTimeoutNT, list);
 return;
 }
 debug('timeout callback %d', msecs);
 var now = TimerWrap.now();
 debug('now: %d', now);
 var diff, timer;
 // 取出队列的尾节点,即最先插入的节点,最可能超时的,TimeOut对象
 while (timer = L.peek(list)) {
 diff = now - timer._idleStart;
 // Check if this loop iteration is too early for the next timer.
 // This happens if there are more timers scheduled for later in the list.
 // 最早的节点的消逝时间小于设置的时间,说明还没超时,并且全部节点都没超时,直接返回
 if (diff < msecs) {
 // 算出最快超时的节点还需要多长时间超时
 var timeRemaining = msecs - (TimerWrap.now() - timer._idleStart);
 if (timeRemaining < 0) {
 timeRemaining = 1;
 }
 // 重新设置超时时间
 this.start(timeRemaining);
 debug('%d list wait because diff is %d', msecs, diff);
 return;
 }
 // The actual logic for when a timeout happens.
 // 当前节点已经超时 
 L.remove(timer);
 assert(timer !== L.peek(list));
 if (!timer._onTimeout) {
 if (async_hook_fields[kDestroy] > 0 && !timer._destroyed &&
 typeof timer[async_id_symbol] === 'number') {
 emitDestroy(timer[async_id_symbol]);
 timer._destroyed = true;
 }
 continue;
 }
 // 执行超时处理
 tryOnTimeout(timer, list);
 }

由上可知,TimeWrapper.c里的kOnTimeout字段已经被改写成一个函数,所以底层的定时器超时时会执行上面的代码,即从定时器队列中找到超时节点执行,直到遇到第一个未超时的节点,然后重新设置超时时间。再次启动定时器。

1 回复
回到顶部

AltStyle によって変換されたページ (->オリジナル) /