Eventloop

384 阅读4分钟

操作系统的一些知识

当我按下空格键,操作系统是咋知道的

  1. 键盘下面是个复杂的电路。当我按下空格键会有一个电流,这个电流会触发空格键的信息(比如空格键的信息是个数字101)
  2. 这个101会被传给操作系统
  3. 操作系统通知浏览器这个键盘事件
  4. 浏览器接收到操作系统给他的这个键盘事件,得到空格键信息,就会显示在input里面

其实按下键盘后是存到一个队列里,操作系统是每五秒就去队列里看看有没有信息(这叫轮询)。没有就走,有就传给其他软件等。

EventLoop是虚拟的概念,只是阶段的转移

js只有单线程,那么问题就来了异步的事件,到底是谁在等?答案是C++,底层事件通过轮询实现。

js遇到异步事件,发请求给C++,让c++执行

Node.js

浏览器可以执行js代码,node.js也可以执行js代码,EventLoop是nodejs的概念

nodejs看见setimeout会1. 开启事件循环 2. 执行setimeout js代码 但是两个先后顺序不知道因为两个都需要时间

  1. Node.js的EventLoop有六个阶段。我们只需要了解三个阶段:timers、poll、check
  2. API
  • setTimeout里的函数fn会存在timers 数组里,放进去之后在poll阶段等1000ms,快速进入timers阶段执行fn
  • setImmediate里的函数会存在check阶段。
  • nextTick会放在当前阶段的后面,跳转到下一阶段之前。
  • Promise不讨论了。

settimeout(f1,0)和setmmediate(f2)

循环首先进入poll阶段。

如果event loop开的很快,发现现在没有f1,timers里没东西,那么执行js时从poll开始

如果开的很慢,js(settimeout)执行完了,那么f1在timers里

如果有就进入check阶段执行这些callback。但同时也会检查是否有到期的timer,如果有,就把这些到期的timer的callback按照调用顺序放到timer queue中,之后循环会进入timer阶段执行queue中的 callback。

这两者的顺序是不固定的,收到代码运行的环境的影响。如果两者的queue都是空的,那么loop会在poll阶段停留,直到有一个i/o事件返回,循环会进入i/o callback阶段并立即执行这个事件的callback。

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

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

如果外面再加一层setimeout,那么事件循环早已经开完了,已经变成了poll阶段

Chrome

  1. Chrome的EventLoop有两个阶段:宏任务(一会儿)和微任务(马上)
  2. API
  • setTimeout和setImmediate里的函数会存在宏任务(一会儿)阶段
  • promise.then(fn)的函数在resolve会存在微任务(马上)阶段
  • await转化成promise
  • 浏览器没有C++线程,只会在进行完同步任务后检查一下异步队列。

new Promise(fn)马上执行fn的,有微任务执行微任务,执行完之后执行宏任务


  • - timers 阶段:这个阶段执行 setTimeout 和 setInterval 的回调函数。
  • - I/O callbacks 阶段:不在 timers 阶段、close callbacks 阶段和 check 阶段这三个阶段执行的回调,都由此阶段负责,这几乎包含了所有回调函数。
  • - idle, prepare 阶段(译注:看起来是两个阶段,不过这不重要):event loop 内部使用的阶段(译注:我们不用关心这个阶段)
  • - poll 阶段:获取新的 I/O 事件。在某些场景下 Node.js 会阻塞在这个阶段。
  • - check 阶段:执行 setImmediate() 的回调函数。
  • - close callbacks 阶段:执行关闭事件的回调函数,如 socket.on('close', fn) 里的 fn。

  •  process.nextTick() 并不是 event loop 的一部分。实际上,不管 event loop 当前处于哪个阶段,nextTick 队列都是在当前阶段后就被执行了。放到当前队列最后。


    一般来说,事件循环在poll阶段停留着,等事件到了,或者有check队列

    浏览器的loop详解

    Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。


    Javascript单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。

    MacroTask(宏任务)

    • script全部代码、setTimeoutsetIntervalsetImmediate(浏览器暂时不支持)、I/OUI Rendering
    • script全部代码指的是先执行完所有script代码

    MicroTask(微任务)

    • Promise

    执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去检查微任务(microTask)队列是否为空,如果为空的话,就执行Task(宏任务),否则就一次性执行完所有微任务。

    每次单个宏任务执行完毕后,检查微任务(microTask)队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务(microTask)后,设置微任务(microTask)队列为null,然后再执行宏任务,如此循环。