node事件循环

事件循环

事件循环是 Node.js 处理非阻塞 I/O 操作的机制,事件循环使Node.js可以通过将操作转移到系统内核中来执行非阻塞I/O操作,由于大多数现代内核都是多线程的,因此它们可以处理在后台执行的多个操作。当这些操作之一完成时,内核会告诉Node.js,将适当的回调添加到轮询队列中以最终执行。

事件轮询机制解析

面的图表展示了事件循环操作顺序的简化概览,每个框被称为事件循环机制的一个阶段。每个阶段都有一个FIFO队列来执行回调。当事件循环进入给定的阶段时,它将执行特定于该阶段的任何操作,然后执行该阶段队列中的回调,直到队列用尽或最大回调数已执行。当该队列已用尽或达到回调限制,事件循环将移动到下一阶段。

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

阶段概述

timers

本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。,计时器指定可以执行所提供回调的阈值,在指定的一段时间间隔后,计时器回调将被尽可能早地运行,操作系统调度或其它正在运行的回调可能会延迟它们。轮询阶段控制何时定时器执行

pending callbacks

待定回调:执行延迟到下一个循环迭代的 I/O 回调。此阶段对某些系统操作(如 TCP 错误类型)执行回调。例如,如果 TCP 套接字在尝试连接时接收到 ECONNREFUSED,则某些 *nix 的系统希望等待报告错误。这将被排队以在挂起的回调阶段执行。

idle, prepare

仅系统内部使用

poll

检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。 轮询 阶段有两个重要的功能:

  • 计算应该阻塞和轮询I/O的时间。
  • 然后处理轮询队列里的事件

当事件循环进入轮询阶段且没有被调度的计时器时

  • 如果 轮询 队列 不是空的 ,事件循环将循环访问回调队列并同步执行它们,直到队列已用尽,或者达到了与系统相关的硬性限制。
  • 如果 轮询 队列 是空的
    • 如果脚本被 setImmediate() 调度,则事件循环将结束 轮询 阶段,并继续 检查 阶段以执行那些被调度的脚本。
    • 如果脚本 未被 setImmediate()调度,则事件循环将等待回调被添加到队列中,然后立即执行。

一旦 轮询 队列为空,事件循环将检查 已达到时间阈值的计时器。如果一个或多个计时器已准备就绪,则事件循环将绕回计时器阶段以执行这些计时器的回调。

check

此阶段允许人员在轮询阶段完成后立即执行回调,setImmediate() 回调函数在这里执行

close callbacks

如果套接字或处理函数突然关闭(例如 socket.destroy()),则’close’ 事件将在这个阶段发出。否则它将通过 process.nextTick() 发出。

libuv

libuv是跨平台支持库,最初是为Node.js编写的。它是基于事件驱动的异步I/O模型设计的。该库提供的不只是对不同I/O轮询机制的简单抽象:“句柄”和“流”为套接字和其他实体提供了高级抽象;还提供跨平台文件I/O和线程功能。提供事件循环以及基于回调的I / O和其他活动的通知。libuv提供了一些核心实用程序,例如计时器,无阻塞网络支持,异步文件系统访问,子进程等等。

libuv事件循环遵循通常的单线程异步I/O方法:所有(网络)I/O在非阻塞套接字上执行,套接字使用给定平台上可用的最佳机制进行轮询:Linux上的epoll,OSX上的kqueue和其他BSD,SunOS上的事件端口和Windows上的IOCP。 dd

libuv是一个多平台支持库,主要关注异步I/O。它最初是为供Node.js使用而开发的,但也由Luvit,Julia,pyuv等使用。

  • 由epoll,kqueue,IOCP和事件端口支持的功能齐全的事件循环
  • Asynchronous TCP and UDP sockets
  • Asynchronous DNS resolution
  • Asynchronous file and file system operations
  • File system events
  • ANSI escape code controlled TTY
  • IPC with socket sharing, using Unix domain sockets or named pipes (Windows)
  • Child processes
  • Thread pool
  • Signal handling
  • High resolution clock
  • Threading and synchronization primitives

事件循环

libuv处理从操作系统收集事件或监视其他事件源的责任,并且用户可以注册事件发生时要调用的回调。 系统程序最常见的活动是处理输入和输出,而不是大量的数字运算。与使用常规的输入/输出函数(的问题read,fprintf等等)是它们 阻塞。与处理器的速度相比,实际写入硬盘或从网络读取花费的时间不成比例。在完成任务之前,函数不会返回,因此您的程序什么也不做。对于需要高性能的程序,这是主要障碍,因为其他活动和其他I / O操作一直处于等待状态。

标准解决方案之一是使用线程。每个阻塞的I / O操作均在单独的线程(或线程池)中启动。当阻塞函数在线程中被调用时,处理器可以安排另一个线程运行,这实际上需要CPU。

libuv遵循的方法使用另一种样式,即异步,非阻塞样式。大多数现代操作系统都提供事件通知子系统。例如,read套接字上的常规调用将阻塞,直到发送者实际发送了一些东西为止。而是,应用程序可以请求操作系统监视套接字并将事件通知放入队列中。应用程序可以方便地检查事件(也许在最大程度地使用处理器之前进行一些数字运算)并获取数据。之所以是异步的,是因为应用程序在某一时刻表达了兴趣,然后又在另一点(时间和空间)使用了数据。它是 非阻塞的因为应用程序进程可以自由执行其他任务。这与libuv的事件循环方法非常吻合,因为可以将操作系统事件视为另一个libuv事件。非阻塞确保其他事件可以像它们进入[1]一样快地继续处理。

参考nodejs libuv