事件循环

237 阅读4分钟

运行时

概念

进程

  • 进程是 CPU资源分配的最小单位
  • 进程之间相互独立
  • 主进程可以拥有子进程
  • 进程可以被多个线程共同拆分运行

线程

  • 线程是 CPU调度的最小单位
  • 线程相互独立又可以共享当前进程的内存
  • 线程共同运行来执行进程
  • 浏览器和nodejs均为多线程,js可以通过web worker实现多线程

宏任务

  • 事件
  • 可以理解为不是js引擎去执行的内容
  • 宏任务执行完成后将数据交到宏任务队列

微任务

  • 宏任务执行后的回调代码的消息队列
  • 该队列将全部出队进入执行栈执行后,开启下一个宏任务

运行

  • FIFO
  • 主代码运行栈(异步交予其他线程处理,处理完的交到消息队列,清空后执行事件循环的消息队列)
  • 事件循环(按照消息队列重复执行宏任务和微任务)

V8引擎

  • V8引擎按照栈结构执行js
  • 只能单线程运行

角色

  • Stack(Frame) 执行栈
  • Queue(Message) 消息队列
  • Heap(Object) 堆

顺序

  • 执行后,会进行入栈,先入后出
  • 执行栈清空后消息队列中开始出队

事件循环

  • 宏任务队列 - 出队 -> 宏任务 -> 微任务队列 - 清空 -> ... -> 宏任务队列清空

浏览器

  • GUI渲染线程
  • 执行js线程,该线程阻塞GUI线程
  • 定时器线程
  • 事件触发线程
  • 异步请求线程

Macro-Task

  • 异步任务的主体
  • setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染等

Micro-Task

  • 异步任务执行后的任务
  • new Promise().then(回调)、MutationObserver(html5新特性) 等
  • 回调函数中的代码

执行

  • 先执行执行栈,执行栈清空后执行宏任务出队后的任务
  • 可以有多个宏任务队列,但只有一个微任务队列
  • 宏任务一个个执行,微任务一队一队执行
  • 执行完宏任务后有微任务,则执行整队微任务,微任务队列清空后,然后在执行队列中的下一个宏任务

nodejs(11版本及之后版本)

  • nodejs运用V8引擎解析js,使用libuv处理i/o
  • 微任务与宏任务和浏览器基本一致
  • 执行栈清空后,先执行宏任务出队,清空微任务队列,再执行出队
  • process.nextTick在当前代码执行完,事件循环执行前执行

宏任务

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

回调队列(微队列)

  • 回调

轮询队列

  • 包含于宏任务队列
  • 轮询阶段
    • 计算应该阻塞和轮询 I/O 的时间。
    • 然后,处理轮询队列里的事件
  • 当轮询队列不为空时,执行队列
  • 当轮询队列为空
    • 如果后续有setimmediate调度,进入check阶段
    • 没有,则进入等待状态

示例

// nodejs事件循环

/**
 * timer  setTimeout、setInterval                     内部触发
 * pending callbacks 上一次轮询延迟的循环迭代的I/O回调   内部触发
 * idle, prepare 休眠、准备                         
 * poll 轮询 <-- 命中 --- 程序外部事件返回的数据         外部触发
 * check setImmediate                                 内部触发
 * close callbacks 一些关闭的事件                      内部触发
 */
const net = require('net');

// 开始
console.info('start');

// 开始服务
const socket = net.createServer().listen(3000);

// timer1
setTimeout(() => {
	console.info('timer1');
}, 0);

// 开启事件循环
Promise.resolve().then(() => {

	// 结果:关闭服务 closeCallback timer1 timer2 timer_imm
	// process.nextTick(() => {
	// 	console.info('关闭服务');
	// 	socket.close();
	// });

	// close callback
	socket.on('close', () => {
		console.info('closeCallback');
	});

	// timer2和timer_imm会根据实际运行出现不同顺序
	socket.close();

	// 当阀值到达时,从新进入timer状态
	// timer2
	setTimeout(() => {
		console.info('timer2');
	}, 0);

	// 在事件循环中轮询状态结束后,立即执行check
	setImmediate(() => {
		console.info('timer_imm');
	});
});

// 结束
console.info('end');

特别说明

  • 此内容仅为本人当前阶段的理解,若有不对还请谅解

参考