Event Loop(事件机制)知多少

在讲 Event Loop (事件循环)之前,我们来了解点 node 的东西,来帮助我们更加明白事件循环是干什么的

 

Node 解决了什么

Web 服务器的瓶颈在于并发的用户量。Node 的首要目标是提供一种简单的,用于创建高性能服务器的开发工具。

Node在处理高并发,I/O 密集场景有明显的性能优势

  • 高并发,是指在同一时间并发访问服务器
  • I/O 密集指的是文件操作、网络操作、数据库
  • 相对的有 CPU 密集,CPU 密集指的是逻辑处理运算、压缩、解压、加密、解密

Web 主要场景就是接收客户端的请求读取静态资源和渲染界面,所以 Node 非常适合 Web 应用的开发。

 

进程与线程

进程是操作系统分配资源和调度任务的基本单位,线程是建立在进程上的一次程序运行单位,一个进程上可以有多个线程。

  1. 浏览器线程
    • 用户界面-包括地址栏、前进/后退按钮、书签菜单等
    • 浏览器引擎-在用户界面和呈现引擎之间传送指令(浏览器的主进程)
    • 渲染引擎,也被称为浏览器内核(浏览器渲染进程)
    • 一个插件对应一个进程(第三方插件进程)
    • GPU提高网页浏览的体验( GPU 进程)
  2. 浏览器渲染引擎
    • 渲染引擎内部是多线程的,内部包含 ui 线程和 js 线程
    • js 线程 ui 线程 这两个线程互斥的,目的就是为了保证不产生冲突。
    • ui 线程会把更改的放到队列中,当 js 线程空闲下来的时候,ui 线程再继续渲染
  3. js 单线程
    • js 是单线程,为什么呢?如果多个线程同时操作 DOM ,那页面不会很混乱?这里所谓的单线程指的是主线程是单线程的,所以在 Node 中主线程依旧是单线程的。
    • 因为是单线程,所以所有任务都需要排队,前一个任务结束,后一个任务才能执行,如果前一个任务花费时间较长,后一个任务等待时间也随之变长。
    • js可以做到先把等待中的任务先放一边晾着,去处理后面的任务
    • 于是所有任务可以分为两种,一种是同步任务,另一种是异步任务:同步任务很简单,前面的任务完成后面才能执行,一个接一个地执行任务。异步任务不占用主线程,直接进入“任务队列”中,等任务队列通知主线程,某个任务可以执行了,才会进入主线程执行。
  4. webworker 多线程
    • 它和 js 主线程不是平级的,主线程可以控制 webworker,但是 webworker不能操作 DOM,不能获取 document,window
  5. 其他线程
    • 浏览器事件触发线程(用来控制事件循环,存放 setTimeout、浏览器事件、ajax 的回调函数)
    • 定时触发器线程(setTimeout 定时器所在线程)
    • 异步 HTTP 请求线程(ajax 请求线程)

单线程特点是节约了内存,并且不需要再切换执行上下文。

 

异步执行的运行机制

主线ç¨åä»»å¡éå.jpg

  • 所有同步任务都在主线程上执行,形成一个执行栈。
  • 主线程之外,还存在一个“任务队列”。只要异步任务有了运行结果,就在“任务队列”中放置一个事件。
  • 一旦执行栈中的所有同步任务都执行完毕,就会去“任务队列”中读取新的任务放到执行栈中,再依次执行任务。
  • 只要主线程空了,就会读取任务队列,这就是js的运行机制。这个过程会不断地重复。

 

再说说事件和回调函数

  • 任务队列其实存放的是事件的队列,主程序读取任务队列,其实就是在读有哪些事件罢了
  • 只要指定过回调函数,这些事件发生时就会进入任务队列中,等待主线程读取
  • 异步任务必须指定回调函数,当主线程执行异步任务时,其实就是在执行对应的回调函数
  • 任务队列是一个先进先出的数据结构,排在前面的先执行。当调用栈中的任务空了后,主线程会自动调用任务队列里的任务执行

 

来看看Event Loop

  • 主线程从任务队列中读取任务,这个过程是不断重复的,所以被称为Event Loop(事件循环),从字面意思就清楚了
  • 再来看一张图

æµè§å¨ä¸­çEvent Loop.png

上图中,主线程产生了heap(堆)和stack(栈),栈中的代码调用各种api,然后在任务队列中加入click,load,done等事件,当栈中的任务都执行完后就去调用任务队列中的事件并依次执行。

 

Node

如图(图片是借鉴的):

3.jpg

NodeJs的运行机制:

  1. V8引擎解析js代码
  2. 代码中可能会调用node API,node会交给LIBUV库处理
  3. LIBUV通过阻塞I/O和多线程实现了异步I\O
  4. 将任务的执行结果返回给V8引擎,V8引擎再将结果返回给用户

 

Node中的Event Loop

在LIBUV内部有这样一个事件环机制,在node启动时会初始化事件环

nodeçEvent Loop.png

  • 这里每一个阶段都对应一个事件队列,当Event Loop执行到某一阶段的时候会将该阶段对应的事件依次执行。
  • 当队列执行完毕or执行的数量超过上限的时候,会自动转入下一阶段。 这里我们重点关注一下poll阶段

 

poll阶段

pollé¶æ®µ.png

 

同步,异步 阻塞和非阻塞

  • 阻塞和非阻塞指的是调用者的状态,关注的是程序在等待调用结果时的状态
  • 同步和异步指的是被调用者是如何通知的,关注的是消息通知机制

 

宏任务和微任务

  • macro-task(宏任务):
    • setTimeout, setInterval, setImmediate, I/O
  • micro-task(微任务):
    • process.nextTick,
    • 原生 Promise (有些实现的promise 将 then 方法放到了宏任务中,浏览器默认放到了微任务),
    • Object.observe (已废弃),
    • MutationObserver(不兼容,已废弃)
    • MessageChannel(vue中 nextClick 实现原理)

同步代码先执行,执行是在栈中执行的,微任务大于宏任务,微任务会先执行(栈),宏任务后执行(队列)

 

敲几行代码来理解知识点

《1》宏任务,微任务在浏览器和 node 环境执行顺序不同

// 这个列子里面,包含了宏任务,微任务,分别看看浏览器和node 打印的结果
console.log(1)
// 栈
setTimeout(function(){
    console.log(2)
    // 微任务
    Promise.resolve(100).then(function(){
        console.log('promise')
    })
})   // 如果不写时间,默认是4ms
// 栈
let promise = new Promise(function(resolve, reject){
    console.log(7)
    resolve(100)
}).then(function(data){
    // 微任务
    console.log(data)
})
// 栈
setTimeout(function(){
    console.log(3)
})
console.log(5)
// 浏览器结果:1 7 5 100 2 promise 3
// node 结果:  1 7 5 100 2 3 promise

浏览器和 node 环境执行顺序不同,浏览器是先把一个栈以及栈中的微任务走完,才会走下一个栈。node 环境里面是把所以栈走完,才走微任务

《2》nextTick 和 then 都属于微任务,谁优先执行呢?

process.nextTick(function(){
    console.log('nextTick')
})
Promise.resolve().then(function(){
    console.log('then')
})
// 结果打印:nextTick then

// 再加一个宏任务呢
setImmediate(function(){
    console.log('setImmediate')
})
// 结果打印:nextTick  then  setImmediate

nextTick 会比 其他微任务、宏任务执行快

《3》i/o 文件操作(宏任务),搭配微任务,谁优先执行呢?

let fs = require('fs');
fs.readFile('./1/log',function(){
    console.log('fs')
})
process.nextTick(function(){
    console.log('text')
})
// 结果打印:text  fs

i/o 文件操作(宏任务), 如果有微任务,先执行微任务,再执行文件读取

《4》

setTimeout(() => {
    console.log(1)
    setTimeout(() => {
        console.log(2)
    }, 500);
}, 1000);

setTimeout(() => {
    console.log(3)
    setTimeout(() => {
        console.log(4)
    }, 1000);
}, 500)

//3 1 2 4
// 个人观点:从上往下,所以上面的定时器先注册,时间一样所以执行2再执行4

 

谈谈Event Loop(事件循环)机制

javascript运行机制:Event Loop

总是一知半解的Event Loop

Event Loop 其实也就这点事

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章