内容简介:Node.js 异步原理
写文章写得实在生无可恋,依旧看起了书——《深入浅出 Node.js》,觉得自己确实有错,之后如果有人问我 Node.js 入门怎么入门我可能还是会推荐他网上的教程,但是说到推荐书,这确实是一本很棒的书。
——安利完毕,之后进入正题。
在面试时可能真的会遇到这样的情况,面试官问:请问一下异步是怎么实现的。
之前在听小伙伴说到这个问题的时候一脸黑人问号,毕竟仿佛是一知半解,不得其意,今天总算可以好好聊一聊异步了,也算是一个章节的笔记。不过掌握的还不是很透彻,如果有错误请指出。
为什么会有异步
- 单线程同步模型系统资源利用率低下
- 多线程模型线程切换开销大,多线程编程中的同步问题让人头大
异步让单线程远离阻塞,同时规避了线程切换(恢复现场)的开销,让单一线程在执行 I/O 操作后立即进行其他操作。
异步 I/O 与非阻塞 I/O
尽管异步与非阻塞我们常常一起提及,异步也确实解决了非阻塞的问题,但是在计算机内核的角度,这并不是同一回事。
我们知道,阻塞 I/O 造成了 CPU 等待,浪费了系统资源,而非阻塞 I/O 会在执行完毕后立即返回,如果需要获得数据,需要再次读取。系统通过轮询来获取非阻塞 I/O 的结果,对 CPU 资源又存在浪费。
现在主要的轮询技术有: read
/ select
/ poll
/ epoll
。
其中 epoll
在进入轮询后没有检测到 I/O 事件将会休眠,直到事件发生,利用率较高,但仍然需要花费资源去等待与确认。
我们理想中的异步应该是可以在进行一次 I/O 之后不闲置 CPU,不去关心,直到完成之后执行回调。
现实中也确实有好几种异步方案,在 Node.js 和 Windows 下的 IOCP 中都使用线程池完成异步 I/O。Node.js 中利用 libuv 封装,来判断平台进行兼容。
事件循环
Node.js 中的异步 I/O 当然得提到大名鼎鼎的事件循环了,如果面试只面试到这里,相信每个人都能说出来:
事件循环就是执行一次循环(一个 Tick),就检测是否有事件未处理,如果有,就取出事件及相关回调函数,如果存在关联的回调函数,就执行它们,然后进入下一循环,如果没有,就退出进程。
事件循环就是一个典型的生产者消费者模型,由请求生产,事件循环消费,这个循环由 IOCP / 多线程创建。
首先我们引入请求对象的概念,JavaScript 层传入的参数和方法都被封装在请求对象中,当有可用线程时,我们就会调用对象底层对应的方法。
组装好请求对象,送入线程池等待执行,这就完成了我们的第一步。
执行结束后,将会将结果存储,并且调用方法通知 IOCP 将线程交还给线程池。
之后事件循环观察到执行完的请求,进行处理即可。
整个流程如图(摘自深入浅出 Node.js):
非 I/O 的异步 API
这部分介绍了一下 setTimeout
, process.nextTick
, setImmediate
的异同,可以从这里理解为什么 setTimeout
或者 setInterval
实现计划任务是个不靠谱的行为。
setTimeout
时创建的定时器会被插入到定时器观察者内部的一个红黑树中。每次 Tick 执行时,就会从该红黑树中迭代取出定时器对象,检测是否超时,如果超时就立即执行。
由此我们知道,如果两个定时 1ms 的任务,但是一个任务占用了 4 ms 的时间片,那么下一个任务就定然是不精准的。
行为图(摘自深入浅出 Node.js):
如果你需要立即执行一个任务,就应该使用 process.nextTick()
他可以规定和保证在下一个循环中执行。
此外,Node.js 还有一个 setImmediate()
方法,与 process.nextTick()
的区别是, process.nextTick
的回调函数保存在一个数组中,而 setImmediate
保存在链表中,在同一个 Tick 中会清空数组,但是只执行链表中的一项。此外, process.nextTick
在事件循环的检查中(idle 观察者)高于 setImmediate
(check 观察者)。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Vue 动态组件 & 异步组件原理
- Spring 异步任务的创建、自定义配置和原理
- Vue你不得不知道的异步更新机制和nextTick原理
- Spring 中异步注解 @Async 的使用、原理及使用时可能导致的问题
- C# async await 原理:编译器如何将异步函数转换成状态机
- SpringBoot | :异步开发之异步调用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。