Kalec的网络日志

漂浮的灵魂

HOME 首页 Event Loop事件循环

Event Loop事件循环

技术
2018-03-19 23:33:00

Event Loop事件循环

说实话这一块内容我理解的并不是太好,这是这门语言最核心的东西!我就尝试写一下吧!

前面咱们介绍了js这门语言的执行机制,还弄了两个有趣的例子来看同步异步区别,那今天咱们先来讨论一下事件循环!如有错误,请多多指教!

Event Loop其实也就是异步事件的执行机制。(在异步的情况下执行顺序,比同步和异步混用的情况更加让人混乱,因此决定去一探究竟)

这一部分内容还是先上点源码干货吧,一般咱们的js运行环境使用的是Google的v8引擎,处理io方面用得是libuv。

libuv事件循环源码

毕竟这层面上是c写的,我好多年没看c的东西了。这下就有点麻烦了,我怀着紧张的心情,听了大佬的建议,去看了下事件循环相关的内容(位于libuv/src/unix/core.c的348行)

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__update_time(loop); uv__run_timers(loop); } r = uv__loop_alive(loop); if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT) break; } if (loop->stop_flag != 0) loop->stop_flag = 0; return r; }

还好不是太难呀,事件循环也便是其中while循环依次去运行8个函数迭代,这8个函数也就是事件循环的每一个阶段。终于松了口气,算是大致了解一点了。

官方文档解读

咱们现在在打开官方文档

官方文档截图
我就来翻译一下(英语水平有限,别在意!)
文档第一段首先说明了事件循环是让node.js允许执行非阻塞的io操作的策略,由于单线程所以这些操作尽可能的交给操作系统内核来完成。
现代大多处理器都是多核的,他们能够在后台处理多个操作。当操作完成后,内核会通知nodejs添加处理事件的队列执行。

官方就对这个做了个简要的梳理,如图

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

其中,他把源码中idleperpare合并为了一个,隐去了

uv__update_time这个阶段,总共归为6个阶段。

  1. timers定时器阶段,主线程会检查定时器时间是否到期,若满足便回调,否则进入下个阶段。
  2. I/O callbacks执行各种IO回调函数
  3. idle, prepare该阶段只供 libuv 内部调用
  4. Poll向系统获取新的io事件,是一个轮询过程,如果没有io任务会一直等待,若有便处理清空队列
  5. check这个阶段执行Immediate回调
  6. close callbacks执行uv__run_closing_handles阶段

注意:其中还有process.nextTick()是执行在每一个阶段前,也就是说每一个阶段若是有process.nextTick()未被执行便会优先执行!!
每个阶段都有回调函数队列,完成一个队列,才能进行下一个阶段,然后层层循环顺序执行,便称为事件循环。

事件循环示例

先说一下呀,这个,这个,是我乱敲的,别打我。我看着也挺头疼的!

const { readFile, readFileSync } = require('fs') const Events = require('events') class EE extends Events {} const ev = new EE() ev.on('event', () => { console.log('事件触发'); }) readFile('./server.key', 'utf-8', ()=>{console.log('第二阶段 IO 回调 读取文件1')}) setImmediate(() => {console.log('第三阶段 Immediate 回调1')}) setImmediate(() => { console.log('第三阶段 Immediate 回调2') Promise.resolve().then(()=>{ console.log('等待切入下一阶段 Promise 回调2'); process.nextTick(() => {console.log('切换阶段前 nextTick 回调1')}) console.log('同步阻塞IO读文件') readFileSync('./js的运行机制.md', 'utf-8') ev.emit('event') process.nextTick(() => {console.log('切换阶段前 nextTick 回调2')}) }) }) setTimeout(()=>{console.log('第一阶段 setTimeout 回调1')}, 0) setTimeout(()=>{ console.log('第一阶段 setTimeout 回调2') process.nextTick(() => { console.log('切换阶段前 nextTick 回调3'); }) }, 0) setTimeout(()=>{console.log('第一阶段 setTimeout 回调3')}, 10) setTimeout(()=>{console.log('第一阶段 setTimeout 回调4')}, 200) process.nextTick(() => {console.log('切换阶段前 nextTick 回调4')}) Promise.resolve().then(()=> { console.log('等待切入下一阶段 Promise 回调2'); setImmediate(() => {console.log('第三阶段 Immediate 回调3')}) }) readFile('./订阅发布模式.js', 'utf-8', ()=>{ console.log('第二阶段 IO 回调 读取文件2') setImmediate(() => {console.log('第三阶段 Immediate 回调4')}) readFile('./递归测试.py', 'utf-8', ()=>{ console.log('第二阶段 IO 回调 读取文件3'); setTimeout(() => {'第一阶段 setTimeout 回调5'}) }) })

好了敲完了,我胡乱写了一通,看完这段代码,我觉得我头都大了!别急,先自行感受一下执行顺序,再接着往下看。我觉得有人甚至都想来揍我一番了。

咱们就先来根据这个示例捋一捋,表达不准确甚至有错误的地方望大家指出,别打我!

咱们先前谈过那个事件循环原理嘛,其实也就套着那个原理就能接受清楚啦,不过这里又来了一些嵌套。实际上嵌套也便就是那个源码里面的循环啦!

首先先看最外层,在阶段触发前,发现有nextTick果断先执行,然后还是在阶段前执行Promise;接着第一阶段发现定时器0ms到期的果断调用;再然后第二阶段IO,第三阶段Immediate等;这里面遇上异步回调(也就是嵌套里面的任何一个阶段)放到事件队列中,等待前面事件循环周期完成后,执行下一个(嵌套里面的每一个阶段)循环周期,就像上面那个图一样,等待队列全部清空后,结束程序。

0_X7Z0k20cwHHi8UOI.png
好啦,说了这么多,公布运行结果吧!(没静下来细心捋一捋的话,真的看着会很头疼的)

[Running] node "/Users/kalec/Desktop/test/执行顺序.js"
切换阶段前 nextTick 回调4
等待切入下一阶段 Promise 回调2
第一阶段 setTimeout 回调1
第一阶段 setTimeout 回调2
切换阶段前 nextTick 回调3
第三阶段 Immediate 回调1
第三阶段 Immediate 回调2
第三阶段 Immediate 回调3
等待切入下一阶段 Promise 回调2
同步阻塞IO读文件
事件触发
切换阶段前 nextTick 回调1
切换阶段前 nextTick 回调2
第二阶段 IO 回调 读取文件1
第二阶段 IO 回调 读取文件2
第三阶段 Immediate 回调4
第二阶段 IO 回调 读取文件3
第一阶段 setTimeout 回调3
第一阶段 setTimeout 回调4

[Done] exited with code=0 in 0.283 seconds

就酱,今天又开开心心的水了一篇!

留言

  • 暂时没有留言,来留下你的留言吧!

评论

看不清?换一个