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

Event Loop(事件循环) #23

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

Event Loop(事件循环) #23

yangdui opened this issue May 18, 2020 · 0 comments

Comments

@yangdui
Copy link
Owner

yangdui commented May 18, 2020

Event Loop(事件循环)

在Node的官网中有事件循环操作顺序的简化概览:

┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
└───────────────────────────┘

每阶段的作用:

  • 定时器:本阶段执行已经被 setTimeout()setInterval() 的调度回调函数。
  • 待定回调:执行延迟到下一个循环迭代的 I/O 回调(也可以说上一个循环迭代的I/O回调,在本次循环中执行)。
  • idle, prepare:仅系统内部使用。
  • 轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。
  • 检测setImmediate() 回调函数在这里执行。
  • 关闭的回调函数:一些关闭的回调函数,如:socket.on('close', ...)

在每次运行的事件循环之间,Node.js 检查它是否在等待任何异步 I/O 或计时器,如果没有的话,则完全关闭。

上面这幅图就是Node事件循环的总体图。对应的代码是在"deps/uv/src/unix/core.c"中(Node版本为v14.0.0,以unix为例)。

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  int timeout;
  int r;
  int ran_pending;

  r = uv__loop_alive(loop);
  if (!r)
    uv__update_time(loop);

  while (r != 0 && loop->stop_flag == 0) {
    uv__update_time(loop);
    uv__run_timers(loop);
    ran_pending = uv__run_pending(loop);
    uv__run_idle(loop);
    uv__run_prepare(loop);

    timeout = 0;
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
      timeout = uv_backend_timeout(loop);

    uv__io_poll(loop, timeout);
    uv__run_check(loop);
    uv__run_closing_handles(loop);

    if (mode == UV_RUN_ONCE) {
      /* UV_RUN_ONCE implies forward progress: at least one callback must have
       * been invoked when it returns. uv__io_poll() can return without doing
       * I/O (meaning: no callbacks) when its timeout expires - which means we
       * have pending timers that satisfy the forward progress constraint.
       *
       * UV_RUN_NOWAIT makes no guarantees about progress so it's omitted from
       * the check.
       */
      uv__update_time(loop);
      uv__run_timers(loop);
    }

    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }

  /* The if statement lets gcc compile it to a conditional store. Avoids
   * dirtying a cache line.
   */
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;

  return r;
}

这里涉及到libuv,现在不探究libuv。只需要知道代码的作用(总体原理)就好。在Node.js Event Loop 的理解 Timers,process.nextTick()的评价里有对代码详细的注释。

Event Loop通过while不断循环,总是从timers开始,到close callbacks结束,然后又从新开始。

nextTick/Promise

在这个循环中,没有出现nextTick和Promise的身影。但是nextTick和Promise执行的时机是有规定的:每阶段执行完成后执行。意思是在timers执行完后会检查执行nextTick和Promise(当然在第一次执行时,会首先执行next Tick和Promise),后面几个阶段都是这样的。

nextTick和Promise 还有一个特点:会将队列中的回调函数执行完成之后才只能进行下一步,而nextTick和Promise中回调函数中存在nextTick和Promise,那么它们的回调函数也会在本次被执行。这样有可能造成饥饿陷阱。

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

function callback() {
	console.log('nextTick');
	process.nextTick(callback);
}

callback();

结果就是会一直输出nextTick,setTimeout 得不到机会执行。

nextTick和Promise之间是nextTick先于Promise执行

Promise.resolve('promise').then((res) => {
	console.log(res);
});

process.nextTick(() => {
	console.log('nextTick');
});

// nextTick
// promise

其他几个阶段在后面介绍

参考资料

Node.js 事件循环,定时器和 process.nextTick()

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