设为首页 收藏本站
查看: 1030|回复: 0

[经验分享] 《深入浅出nodejs》读书笔记 第3章 异步I/O

[复制链接]

尚未签到

发表于 2017-2-22 10:12:06 | 显示全部楼层 |阅读模式
第三章 异步I/O
 
伴随着异步I/O的还有事件驱动和单线程,它们构成Node的基调。
 
3.1 为什么要异步I/O
 
3.1.1 用户体验
采用异步请求,在下载资源期间,JavaScript和UI的执行都不会处于等待状态,可以继续相应用户的交互行为,给用户一个鲜活的页面。
 
3.1.2 资源分配
Node利用单线程,远离多线程死锁、状态同步等问题;利用异步I/O,让单线程远离阻塞,以更好地利用CPU。
为了弥补单线程无法利用多核CPU的缺点,Node提供了类似前端浏览器总Web Woekers的子进程,该子进程可以通过工作进程高效地利用CPU和I/O。
异步I/O的提出是期望I/O的调用不再阻塞后续运算,将原有等待I/O完成的这段时间分配费其余需要的业务中去执行。
DSC0000.png
 

3.2 异步I/O实现现状
 
3.2.1 异步I/O与非阻塞I/O
从实际效果而言,异步和非阻塞都达到了我们并行I/O的目的。但是从计算机内核I/O而言,异步/同步和阻塞/非阻塞实际上是两回事。
操作系统内核对于I/O只有两种方式:阻塞与非阻塞。
阻塞I/O的一个特点是调用之后一定要等到系统内核层面完成所有操作后,调用才结束。阻塞I/O造成CPU等待I/O,浪费等待时间,CPU的处理能力不能得到充分利用。
DSC0001.png
 

  非阻塞I/O,调用之后会立即返回。
DSC0002.png
但非阻塞I/O也存在一些问题。由于完整的I/O并没有完成,立即返回的并不是业务层期望的数据,而仅仅是当前调用的状态。为了获取完整的数据,应用程序需要重复调用I/O操作来确认是否完成。
  这种重复调用判断操作是否完成的技术叫做轮询。
  现存的轮询技术主要有一下这些:
  read
  select
  poll
  epoll:Linux下最高效的I/O事件通知机制。在进入轮询的时候如果没有检查到I/O事件,将会进行休眠,直到事件发生将它唤醒。
  kqueue
  3.2.2 理想的非阻塞异步I/O
  我们期望的完美的异步I/O应该是应用程序发起非阻塞调用,无须通过便利或者事件唤醒等待方式轮询,可以直接处理下一个任务,只需要在I/O完成后通过信号或回调将数据传递给应用程序即可。
DSC0003.png
 幸运的是,在Linux下存在这样的方式。但不幸的是,只有Linux下有,而且它还有缺陷(无法利用系统缓存)。
  3.2.2 现实的异步I/O
DSC0004.png
 Linux的自定义线程池、Windows平台下的IOCP。
DSC0005.png
另一个需要强调的地方在于我们时常提到Node是单线程的,这里的单线程仅仅只是JavaScript执行在单线程中罢了。在Node中,无论是*nix还是Windows平台,内部完成I/O任务的另有线程池。
  3.3 Node的异步I/O
  3.3.1 事件循环
  事件循环是Node自身的执行模型。
DSC0006.png
 
  3.3.2 观察者
  在每个Tick的过程中,如何判断否有时间需要处理呢?引入观察者,类似观察这模式思想。
  在Node中,事件主要来源于网络请求、文件I/O等,这些事件对应的观察者有文件I/O观察者、网络I/O观察者等。
  3.3.3 请求对象
  对于Node中的异步I/O调用而言,回调函数却不由开发者来调用。
  事实上,从JavaScript发起调用到内核执行完I/O操作的过渡过程中,存在一种中间产物,它叫做请求对象。
  Node机制是,把回调函数赋值到请求对象的属性上,再把请求对象放到当前线程池中。当线程池中有可用线程时,就会执行回调方法。
  至此,JavaScript调用立即返回,有JavaScript层面发起的异步调用的第一阶段就此结束。JavaScript线程可以继续执行当前任务的后续操作。当前的I/O操作在线程池中等待执行。不管他是否阻塞I/O,都不会影响到JavaScript线程的后续执行,如此就达到了异步的目的。
  请求对象是异步I/O过程中的重要中间产物,所有的状态都保存在这个对象中,包括送入线程池等待执行以I/O操作完毕后的回调处理。
  3.3.4 执行回调
  组装好请求对象,送入I/O想城池等待执行,实际上完成了异步I/O的第一部分,回调通知是第二部分。
  当线程池中的I/O操作调用完毕后,会将获取的结果储存在req对象的result属性上,然后调用方法通知IOCP,告知当前对象操作已经完成。
  在这个过程中,其实还动用了实现循环的I/O观察者。在每次Tick的执行中,他会调用IOCP相关方法检查线程池中是否有执行完的请求,若果存在,会将请求对象加入到I/O观察者的对了中,等待处理。
  I/O观察者调用函数的行为就是取出请求对象的result属性作为参数,取出complete_sys属性作为方法,然后调用执行。以此达到调用JavaScript中传入的回调函数的目的。
DSC0007.png
 事件循环、观察者、请求对象、I/O线程池这四者共同构成了Node异步I/O模型的基本要素。
  Windows下主要通过IOCP来向系统内核发送I/O请求和从内核获取已完成的I/O操作,配以事件循环,以此完成异步I/O的过程。
  3.3.5 小结
  记住,在Node中,除了JavaScript是单线程外,Node自身其实是多线程的,只是I/O线程使用的CPU较少。另一个需要重视的观点则是,除了用户代码无法执行外,所有I/O(磁盘I/O和网络I/O)则是可以并行起来的。
  3.4 非I/O的异步API
  尽管我们在介绍Node的时候,多数情况下都会提到异步I/O,但是Node中其实还存在一些与I/O无关的异步API,它们分别是setTimeout()、setInterval()、setImmediate()和process.nextTick()。 
  3.4.1 定时器
  调用setTimeout()或者setInterval()创建的定时器会被插入到定时器观察者内部的一个红黑树中。每次Tick执行时,回从该红黑树中迭代取出定时器对象,检查是否超过定时实际,如果超过,就形成一个事件,它的回调函数将立即执行。
  定时器的问题在于,它并非精确的(在容忍范围内)。
  3.4.2 process.nextTick()
  process.nextTick()比setTimeout()方法效率更高。
  3.4.3 setImmediate()
  setImmediate()方法与process.nextTick()方法十分类似,都是将回调函数延迟执行。

process.nextTick(function(){
console.log("nextTick延迟执行");
});
setImmediate(function(){
console.log("setImmediate延迟执行");
});
console.log("正常执行");
   执行结果:
  正常执行
  nextTick延迟执行
  setImmediate延迟执行
  从结果里可以看到,process.nextTick()中的回调函数执行的优先级要高于setImmediate()。这里的原因在于事件循环对观察者的检查是又先后顺序的,process.next()属于idle观察者,setTimediate()属于check观察者。在每一轮循环检查中,idle观察者先于I/O观察者,I/O观察者先于check观察者。
  在具体实现上,process.nextTick()的回调函数保存在一个数组中,setImmediate()的结果则是保存在链表中。在行为上,process.nextTick()在每轮循环中回将数组中回调函数全部执行完,而setImmediate()在每轮循环中执行链表中的一个回调函数。
  3.5 事件驱动与高性能服务器
  事件驱动的实质:通过主循环加事件触发的方式来运行程序。
DSC0008.png
下面为几种经典的服务器模型,这里对比下它们的优缺点:
  同步试。对于同步试的服务,一次只能处理一个请求,并且其余请求都处于等待状态。
  每进程/每请求。为每个请求启动一个进程,这样可以处理多个请求,但是他不具备扩展性,因为系统资源只有那么多。
  每线程/每请求。为每个请求启动一个线程来处理。尽管线程比进程要轻量,但是由于每个线程都占用一定内存,当大并发请求到来时,内存将会很快用光,导致服务器缓慢。每线程/每请求的扩展性比每进程/每请求的方式要好,但对于大型站点而言依然不够。
  Node通过实践驱动的方式处理请求,无须为每一个请求创建额外的线程,可以省掉创建线程和销毁线程的开销,同事操作系统在调度任务时因为线程较少,上线文切换的代价很低。这是Node高性能的一个原因。
  3.6 总结
  事件循环是异步实现的核心,它与浏览器中的执行模型基本保持了一致。
 
 
 
 

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-345566-1-1.html 上篇帖子: 《深入浅出nodejs》读书笔记 第2章 模块机制 下篇帖子: nodejs中使用javascript的prototype特性进行日期格式化
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表