Node的异步I/O一探

Node的异步I/O

我们为什么需要异步I/O?

  • 用户体验
    服务器端如果基于同步执行的,随着应用复杂性的增加,响应的总耗时为M+N+...的总时间,但是异步执行的话,总耗时则为M、N、...中耗时最长的一个,能够更快速响应资源,让前端的体验更好
  • 资源分配
    Node利用单线程,远离多线程、状态同步等问题,利用异步I/O,让单线程远离阻塞,以更好地利用CPU

异步I/O与非阻塞I/O

操作系统内核对于I/O只有两种方式:阻塞与非阻塞
阻塞I/O: 调用之后一定要等到系统内核层面完成所有操作后,调用才结束
阻塞I/O造成CPU等待I/O,浪费等待时间,CPU的处理能力不能得到充分利用。
非阻塞I/O:调用后会立刻不带数据立刻返回(返回的仅仅是当前调用的状态),要获取数据,还需要通过文件描述符进行再次读取。
应用程序需要重复调用I/O操作来确认是否完成,称为轮询

主要的轮询技术

  • read。最原始,性能最低的一种,通过重复调用来检查I/O的状态来完成完整数据的读取
  • select。通过对文件描述符的事件状态来进行判断(仅轮询一次即可,可以同时检查1024个文件描述符,但是会持续等待到数据读取完成为止)
  • poll。与select类似,但是采用链表的方式来存储状态。其次它能避免不需要的检查
  • epoll。该方案是Linux下效率最高的的I/O事件通知机制,在进入轮询的时候如果没有检查到I/O事件,将会进行休眠,直到事件发生将它唤醒。不会浪费CPU,执行效率较高。
  • kquue。与epoll类似,不过仅在FreeBSD系统下存在

现实的异步I/O

通过让部分线程进行阻塞I/O或者非阻塞I/O加轮询技术来完成数据获取,让一个线程进行计算处理,通过线程之间的通信将I/O得到的数据进行传递,实现异步I/O
因此,Javascript只需在单线程(主线程)中执行,内部完成I/O任务的另有线程池。


IMG_20171001_170208.jpg

Node的异步I/O

事件循环

在进程启动时,Node便会创建一个类似while(true)的循环,每执行一次循环体的过程称为Tick,每个Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调函数进而执行,然后进入下个循环,如果不再有事件处理,则跳出流程


IMG_20171001_190115.jpg
观察者

每个Tick过程中,由一个或多个观察者来判断是否有要处理的事件。
异步I/O、网络请求等是事件的生产者,源源不断为Node提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环则从观察者那里取出事件并处里

请求对象

事实上,从Javascript发起调用到内核执行完成I/O操作的过渡过程中,存在一种中间产物,叫做请求对象
拿fs.open()作为例子
(1)fs.open()根据路径和参数去打开一个.cc文件(C++内建模块),从而得到一个文件描述符
(2)然后这个.cc文件经过libuv平台判断调用对应平台的uv_fs_open()方法
(3)在uv_fs_open()调用过程中,创建了一个FSReqWrap请求对象,而从Javascript层传入的参数和当前方法都会封装到这个请求对象上,而回调函数则被设置在这个对象的oncomplete_sym属性上

req_wrap->object->Set(oncpmplete_sym,callback);

(4)对象包装完成后,在Windows下,调用QueueUserWorkItem()方法将这个FSReaWrap对象推入线程池中等待执行

QueueUserWorkItem(&uv_fs_thread_proc,req,WT_EXECUTEDEFAULT)
/*
接收三个参数:
①将要执行的方法的引用
②将要执行的方法运行时所需要的参数
③执行的标志
*/

(5)当有可用线程时,会调用uv_fs_thread_proc()方法,这个方法会根据传入参数的类型调用相应的底层函数。以uv_fs_open()为例,实际上调用的是fs_open()方法
(6)至此,Javascript调用立即返回,由Javascript层面发起的异步调用的第一阶段就此结束,因此javascript可继续执行其他操作,从而达到异步的目的

执行回调(处理请求对象)

(1)线程中的I/O操作调用完毕之后,会将获取的结果储存在req->result属性上,然后调用PostQueuedCompletionStatus()通知IOCP,告知当前对象操作已经完成

PostQueuedCompletionStatus()方法的作用是向IOCP提交执行状态,并把线程归还线程池,这个状态可以通过GetQueuedCompletionStatus()提取
(2)每次Tick的执行中,观察者会调用IOCP相关的GetQueuedCompletionStatus()检查线程池中是否有执行完的请求,如果存在,会把请求对戏那个加入到I/O观察者的队列中,然后将其当做事件处理
(3)取出请求对象的result属性作为参数,取出oncomplete_sym属性(传入的回调函数)作为方法,然后调用执行,以此达到调用Javascript中传入的回调函数的目的。


IMG_20171001_190136.jpg
总结

一个异步I/O经历了请求对象I/O线程池观察者事件循环这四个步骤,构成了异步I/O模型的基本要素。windows下主要通过IOCP来向系统内核发送I/O调用和从内核获取已完成的I/O操作,配以事件循环,以此完成异步I/O的过程。

非I/O的异步API

  • 定时器
    调用setTimeout()或者setInterval()创建的定时器会被插入到定时器观察者内部的一个红黑树中,每次Tick执行时,会从该红黑树中迭代取出定时器对象,检查是否超过定时事件,如果超过就形成一个事件,它的回调函数将会被推入handles中排队等候被执行
  • process.nextTick()
    每次调用process.nextTick()方法,只会将回调函数放入队列中,在下一轮Tick取出执行。复杂度更低,性能比setTimeout更高效。
  • setImmediate()
    与process.nextTick()类似,但process.nextTick中的回调函数执行的优先级要高于setImmediate(),因为事件循环对观察者的检查是有先后顺序的,process.nextTick()属于idle观察者,setImmeditate属于check观察者。

以上参考《深入浅出Node.js》一书

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容