Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

setTimeout/setImmediate #22

Open
yangdui opened this issue May 18, 2020 · 0 comments
Open

setTimeout/setImmediate #22

yangdui opened this issue May 18, 2020 · 0 comments

Comments

@yangdui
Copy link
Owner

yangdui commented May 18, 2020

setTimeout/setImmediate

setTimeout

先看setTimeout函数:

function setTimeout(callback, after, arg1, arg2, arg3) {
  validateCallback(callback);

  let 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, true);
  insert(timeout, timeout._idleTimeout);

  return timeout;
}

通过后面两行代码:

const timeout = new Timeout(callback, after, args, false, true);
insert(timeout, timeout._idleTimeout);

首先实例化Timeout,将timeout插入队列中

在Timeout函数中

function Timeout(callback, after, args, isRepeat, isRefed) {
  after *= 1; // Coalesce to number or NaN
  if (!(after >= 1 && after <= TIMEOUT_MAX)) {
    if (after > TIMEOUT_MAX) {
      process.emitWarning(`${after} does not fit into` +
                          ' a 32-bit signed integer.' +
                          '\nTimeout duration was set to 1.',
                          'TimeoutOverflowWarning');
    }
    after = 1; // Schedule on next tick, follows browser behavior
  }
  
  ....

如果超时时间小于1或者大于TIMEOUT_MAX时,会将超时时间重置为1。

回到 Event Loop 中的uv__run_timers函数

void uv__run_timers(uv_loop_t* loop) {
  struct heap_node* heap_node;
  uv_timer_t* handle;

  for (;;) {
    heap_node = heap_min(timer_heap(loop));
    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);
    uv_timer_again(handle);
    handle->timer_cb(handle);
  }
}

总体是一个for循环,取出超时时间(这里的超时时间时经过处理的,简单理解时将超时时间加上当时时间)最小节点,判断这个超时时间,如果小于现在时间就执行回调,然后继续循环。如果超时时间大于现在时间,证明时间还没到,跳出循环,结束函数(因为是最小二叉堆,所以第一个没有超时,后面的就不需要处理)。

setImmediate

setImmediate和setTimeout差不多,只不过少了超时时间。

function setImmediate(callback, arg1, arg2, arg3) {
  validateCallback(callback);

  let 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);
}

setImmediate函数中实例化了Immediate

const Immediate = class Immediate {
  constructor(callback, args) {
    this._idleNext = null;
    this._idlePrev = null;
    this._onImmediate = callback;
    this._argv = args;
    this._destroyed = false;
    this[kRefed] = false;

    initAsyncResource(this, 'Immediate');

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

    immediateQueue.append(this);
  }
  ....

在Immediate中会将this插入immediateQueue队列中。

在Event Loop事件循环中uv__run_check函数。这个函数是通过宏生成的,在loop-watcher.c文件中

#define UV_LOOP_WATCHER_DEFINE(name, type)                                    \
    ...

    void uv__run_##name(uv_loop_t* loop) {                                      \
        uv_##name##_t* h;                                                         \
        QUEUE queue;                                                              \
        QUEUE* q;                                                                 \
        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);                                                        \
        }                                                                         \
      }  

    ...
}

UV_LOOP_WATCHER_DEFINE(check, CHECK)

在uv__run_check函数中,总体就是取出队列中的节点执行。

nextTick/Promise 执行时机(以setTimeout为例)

在介绍nextTick/Promise时,知道nextTick/Promise会在每个阶段后执行,这里以setTimeout为例说明。在setTimeout函数中会执行insert函数:

function insert(item, msecs, start = getLibuvNow()) {
  // Truncate so that accuracy of sub-milisecond timers is not assumed.
  msecs = MathTrunc(msecs);
  item._idleStart = start;

  // Use an existing list if there is one, otherwise we need to make a new one.
  let list = timerListMap[msecs];
  if (list === undefined) {
    debug('no %d list was found in insert, creating a new one', msecs);
    const expiry = start + msecs;
    timerListMap[msecs] = list = new TimersList(expiry, msecs);
    timerListQueue.insert(list);

    if (nextExpiry > expiry) {
      scheduleTimer(msecs);
      nextExpiry = expiry;
    }
  }

  L.append(list, item);
}

其中scheduleTimer(msecs)更新超时时间。

scheduleTimer函数执行流程

void ScheduleTimer(const FunctionCallbackInfo<Value>& args) {
  auto env = Environment::GetCurrent(args);
  env->ScheduleTimer(args[0]->IntegerValue(env->context()).FromJust());
}

void Environment::ScheduleTimer(int64_t duration_ms) {
  if (started_cleanup_) return;
  uv_timer_start(timer_handle(), RunTimers, duration_ms, 0);
}

其中uv_timer_start函数就是更新超时时间(duration_ms)的(libuv采用最小二叉堆数据结构)。

在Event Loop 的timers阶段,判断是否超时,如果超时了执行RunTimers函数。

RunTimers函数定义在env.cc文件中

void Environment::RunTimers(uv_timer_t* handle) {
  Local<Function> cb = env->timers_callback_function();
  ...
  do {
    ret = cb->Call(env->context(), process, 1, &arg);
  } while (ret.IsEmpty() && env->can_call_into_js());

  ...
}

在RunTimers函数中会执行timers_callback_function函数,timers_callback_function函数是在node.js初始化的:

setupTimers(processImmediate, processTimers);
void SetupTimers(const FunctionCallbackInfo<Value>& args) {
  CHECK(args[0]->IsFunction());
  CHECK(args[1]->IsFunction());
  auto env = Environment::GetCurrent(args);

  env->set_immediate_callback_function(args[0].As<Function>());
  env->set_timers_callback_function(args[1].As<Function>());
}

timers_callback_function函数就是processTimers函数。processTimers又调用listOnTimeout。

在listOnTimeout函数中,执行完一次setTimeout回调函数之后就要检查时候应该执行nextTick回调函数

function listOnTimeout(list, now) {
	...

    let ranAtLeastOneTimer = false;
    let timer;
    while (timer = L.peek(list)) {
     ...

      if (ranAtLeastOneTimer)
        runNextTicks();
      else
        ranAtLeastOneTimer = true;
		
		...

      try {
        const args = timer._timerArgs;
        if (args === undefined)
          timer._onTimeout();
        else
          timer._onTimeout(...args);
      } finally {
        if (timer._repeat && timer._idleTimeout !== -1) {
          timer._idleTimeout = timer._repeat;
          insert(timer, timer._idleTimeout, start);
        } else if (!timer._idleNext && !timer._idlePrev && !timer._destroyed) {
          timer._destroyed = true;

          if (timer[kRefed])
            refCount--;

          if (destroyHooksExist())
            emitDestroy(asyncId);
        }
      }

      emitAfter(asyncId);
    }

    ...
  }

第一次进入listOnTimeout函数时,因为ranAtLeastOneTimer为false,所以不会执行runNextTicks函数,执行完第一个setTimeout时,会进入runNextTicks函数。

setTimeout(() => {
	console.log('setTimeout1');
	process.nextTick(() => {
		console.log('nextTick');
	});
}, 1);

setTimeout(() => {
	console.log('setTimeout2');
}, 1);

setTimeout(() => {
	console.log('setTimeout3');
}, 1);

// setTimeout1
// nextTick
// setTimeout2
// setTimeout3

setTimeout、setImmediate执行顺序

setTimeout(() => {
	console.log('setTimeout');
}, 0);

setImmediate(() => {
	console.log('setImmediate');
});

这段代码执行结果是不固定的

setTimeout和setImmediate都有可能在第一位。

在介绍setTimeout时,我们知道如果超时时间小于1或者大于TIMEOUT_MAX时,都会重置为1。所以当程序执行到uv_run(进入Event Loop时),这段时间经过多久是未知的,如果cpu不忙可能时间小于1,那么此时setTimeout回调函数不会执行,只能等待下次循环。那么setImmediate回调函数首先执行。如果cpu很忙,执行uv_run时,时间已经超过1毫秒,那么setTimeout回调函数执行。

参考资料

【nodejs原理&源码杂记(8)】Timer模块与基于二叉堆的定时器](https://www.cnblogs.com/huaweicloud/p/11861536.html)

nodejs 14.0.0源码分析之setTimeout

Node.js Event Loop 的理解 Timers,process.nextTick()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant