异步IO

    在操作系统中,程序运行的空间分为内核空间和用户空间。我们常常提起的异步I/O,其实质是用户空间中的程序不用依赖内核空间中的I/O操作实际完成,即可进行后续任务。

同步IO的并行模式

  • 多线程单进程

多线程的设计之处就是为了在共享的程序空间中,实现并行处理任务,从而达到充分利用CPU的效果。多线程的缺点在于执行时上下文交换的开销较大,和状态同步(锁)的问题。同样它也使得程序的编写和调用复杂化。

  • 单线程多进程
    为了避免多线程造成的使用不便问题,有的语言选择了单线程保持调用简单化,采用启动多进程的方式来达到充分利用CPU和提升总体的并行处理能力。它的缺点在于业务逻辑复杂时(涉及多个I/O调用),因为业务逻辑不能分布到多个进程之间,事务处理时长要远远大于多线程模式。

异步IO的必要性

采用同步方式的程序要完成这两个任务的时间总花销会是m + n。但是如果是采用异步方式的程序,在两种I/O可以并行的状况下(比如网络I/O与文件I/O),时间开销将会减小为max(m, n)。而当并行任务更多的时候,m + n + …与max(m, n, …)之间的孰优孰劣更是一目了然。Node.js天然地支持这种异步I/O,这是众多云计算厂商对其青睐的根本原因。

操作系统对异步I/O的支持

    异步与非阻塞听起来似乎是同一回事。从实际效果的角度说,异步和非阻塞都达到了我们并行I/O的目的。但是从计算机内核I/O而言,异步/同步和阻塞/非阻塞实际上时两回事。
  • I/O的阻塞与非阻塞
    阻塞模式的I/O会造成应用程序等待,直到I/O完成。同时操作系统也支持将I/O操作设置为非阻塞模式,这时应用程序的调用将可能在没有拿到真正数据时就立即返回了,为此应用程序需要多次调用才能确认I/O操作完全完成。
  • I/O的同步与异步
    I/O的同步与异步出现在应用程序中。如果做阻塞I/O调用,应用程序等待调用的完成的过程就是一种同步状况。相反,I/O为非阻塞模式时,应用程序则是异步的。

异步I/O与轮询技术

    当进行非阻塞I/O调用时,要读到完整的数据,应用程序需要进行多次轮询,才能确保读取数据完成,以进行下一步的操作。
    轮询技术的缺点在于应用程序要主动调用,会造成占用较多CPU时间片,性能较为低下。现存的轮询技术有以下这些:
  • read:通过重复调用来检查I/O的状态来完成完整数据读取,性能也是最低的一种。
  • select:通过对文件描述符上的事件状态来进行判断。
  • poll
  • epoll
  • pselect
  • kqueue
    轮询技术满足了异步I/O确保获取完整数据的保证。但是对于应用程序而言,它仍然只能算时一种同步,因为应用程序仍然需要主动去判断I/O的状态,依旧花费了很多CPU时间来等待。

理想的异步I/O模型

    理想的异步I/O应该是应用程序发起异步调用,而不需要进行轮询,进而处理下一个任务,只需在I/O完成后通过信号或是回调将数据传递给应用程序即可。
  

不同操作系统的异步IO方案

  • Linux

在Linux下存在一种这种方式,它原生提供了一种异步非阻塞I/O方式(AIO)即是通过信号或回调来传递数据的。不幸的是,只有Linux下有这么一种支持,而且还有缺陷(AIO仅支持内核I/O中的O_DIRECT方式读取,导致无法利用系统缓存.

    另一种理想的异步I/O是采用阻塞I/O,但加入多线程,将I/O操作分到多个线程上,利用线程之间的通信来模拟异步.
    Linux平台下没有完美的异步I/O支持。所幸的是,libev的作者Marc Alexander Lehmann重新实现了一个异步I/O的库:libeio。libeio实质依然是采用线程池与阻塞I/O模拟出来的异步I/O。

  • Windows
    Windows有一种独有的内核异步IO方案:IOCP。IOCP的思路是真正的异步I/O方案,调用异步方法,然后等待I/O完成通知。IOCP内部依旧是通过线程实现,不同在于这些线程由系统内核接手管理。IOCP的异步模型与Node.js的异步调用模型已经十分近似。

Node.js的异步IO方案

    由于Windows平台和*nix平台的差异,Node.js提供了libuv来作为抽象封装层,使得所有平台兼容性的判断都由这一层次来完成,保证上层的Node.js与下层的libeio/libev及IOCP之间各自独立。Node.js在编译期间会判断平台条件,选择性编译unix目录或是win目录下的源文件到目标程序中。

Node.js的异步IO模型

    Node.js的回调函数究竟是如何被调用的?在文件I/O这一块与普通的业务逻辑的回调函数不同在于它不是由我们自己的代码所触发,而是系统调用结束后,由系统触发的。
    下面我们以最简单的fs.open方法来作为例子,探索Node.js与底层之间是如何执行异步I/O调用和回调函数究竟是如何被调用执行的。
 fs.open = function(path, flags, mode, callback) {
callback = arguments[arguments.length - 1];
if (typeof(callback) !== 'function') {
callback = noop;
}
mode = modeNum(mode, 438 /*=0666*/);
binding.open(pathModule._makeLong(path), stringToFlags(flags), mode, callback); };

fs.open的作用是根据指定路径和参数,去打开一个文件,从而得到一个文件描述符,是后续所有I/O操作的初始操作。

 
    在JavaScript层面上调用的fs.open方法最终都透过node_file.cc调用到了libuv中的uv_fs_open方法,这里libuv作为封装层,分别写了两个平台下的代码实现,编译之后,只会存在一种实现被调用。
  • 请求对象
    在uv_fs_open的调用过程中,Node.js创建了一个FSReqWrap请求对象。从JavaScript传入的参数和当前方法都被封装在这个请求对象中,其中回调函数则被设置在这个对象的oncomplete_sym属性上。
req_wrap->object_->Set(oncomplete_sym, callback);
    对象包装完毕后,调用QueueUserWorkItem方法将这个FSReqWrap对象推入线程池中等待执行。
QueueUserWorkItem(&uv_fs_thread_proc, req, WT_EXECUTELONGFUNCTION)
    QueueUserWorkItem接受三个参数,第一个是要执行的方法,第二个是方法的上下文,第三个是执行的标志。当线程池中有可用线程的时候调用uv_fs_thread_proc方法执行。该方法会根据传入的类型调用相应的底层函数,以uv_fs_open 为例,实际会调用到fs__open方法。调用完毕之后,会将获取的结果设置在req->result上。然后调用PostQueuedCompletionStatus通知我们的IOCP对象操作已经完成。
PostQueuedCompletionStatus((loop)->iocp, 0, 0, &((req)->overlapped))
    PostQueuedCompletionStatus方法的作用是向创建的IOCP上相关的线程通信,线程根据执行状况和传入的参数判定退出。至此,由JavaScript层面发起的异步调用第一阶段就此结束。
  • 事件循环
    在调用uv_fs_open方法的过程中实际上应用到了事件循环。以在Windows平台下的实现中,启动Node.js时,便创建了一个基于IOCP的事件循环loop,并一直处于执行状态。
uv_run(uv_default_loop());
    每次循环中,它会调用IOCP相关的GetQueuedCompletionStatus方法检查是否线程池中有执行完的请求,如果存在,poll操作会将请求对象加入到loop的pending_reqs_tail属性上。另一边这个循环也会不断检查loop对象上的pending_reqs_tail引用,如果有可用的请求对象,就取出请求对象的result属性作为结果传递给oncomplete_sym执行,以此达到调用JavaScript中传入的回调函数的目的。至此,整个异步I/O的流程完成结束。
    其流程如下:
 
    事件循环和请求对象构成了Node.js的异步I/O模型的两个基本元素,这也是典型的消费者生产者场景。在Windows下通过IOCP的GetQueuedCompletionStatus、PostQueuedCompletionStatus、QueueUserWorkItem方法与事件循环实。对于*nix平台下,这个流程的不同之处在与实现这些功能的方法是由libeio和libev提供。
  

Node.js入门:异步IO的更多相关文章

  1. 【译】深入理解python3.4中Asyncio库与Node.js的异步IO机制

    转载自http://xidui.github.io/2015/10/29/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3python3-4-Asyncio%E5%BA%93% ...

  2. Node.js的异步IO和事件轮询

     想象一下,以前我们在写程序时, 如果程序在I/O上阻塞了,当有更多请求过来时,服务器会怎么处理呢?在这种情景中通常会用多线程的方式.一种常见的实现是给每个连接分配一个线程,并为那些连接设置一个线程池 ...

  3. 极简 Node.js 入门 - 2.4 定时器

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  4. Node.js入门:事件机制

    Evented I/O for V8 JavaScript     基于V8引擎实现的事件驱动IO.   事件机制的实现     Node.js中大部分的模块,都继承自Event模块(http://n ...

  5. Node.js 入门手册:那些最流行的 Web 开发框架

    这篇文章与大家分享最流行的 Node.js Web 开发框架.Node 是一个服务器端 JavaScript 解释器,它将改变服务器应该如何工作的概念.它的目标是帮助程序员构建高度可伸缩的应用程序,编 ...

  6. 《Node.js入门》CentOS 6.5下Node.js Web开发环境搭建笔记

    近期想尝试一下英特尔的基于WebRTC协同通信开发套件,所以须要在本地搭建Node.js Web的开发測试环境. 这里讲的是CentOS 下的搭建方法.使用Windows的小伙伴请參考: <No ...

  7. Node.js之异步编程

    > 文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. ![file](https://img2018.cnblogs.com/blog/830272/20 ...

  8. Node.js 入门到干活,10 个优质项目就够了!

    Node.js 在很多大公司都有不错的实践,比如:淘宝.天猫 Web 版,很多页面都是在 Node 服务器上渲染的.还有各种脚手架.前端打包发布工具.构建生态的小工具,也基本都是 Node.js 编写 ...

  9. 极简 Node.js 入门 - Node.js 是什么、性能有优势?

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

随机推荐

  1. css高度自适应

    何为高度自适应? 高度自适应就是高度能跟随浏览器窗口的大小改变而改变,典型的运用在一些后台界面中上面一栏高度固定用作菜单栏或导航栏,下面一栏高度自适应用于显示内容.高度自适应不像宽度自适应那样简单,在 ...

  2. 动态加载js文件

    由于最近在弄echarts,关于地图类的效果,但是全国地图整体的js文件太大了,加载很耗费资源,所以要根据不同省份加载不同地区的js地图, 于是就想的比较简单, var script = docume ...

  3. Android studio 加速编译方法

    JRebel for Android 是一个Android Studio的插件,可以大大加速Android Studio的编译速度,对于小项目来说或许不明显:但是当项目达到一定的规模时,它对于Andr ...

  4. RichTextBox着色与着色不闪

    近来写的一个数据查询分析器意外的快捷,不到两晚工夫就搞定了.完成度相当的高.当然少不了关键字着色,不过着色的代码来自的网上,看了一下感觉过多的循环 //文本框改变事件 int index = this ...

  5. VIM使用(一) VIM插件管理利器-vundle

    有关VIM的文件网上一大堆,这里只是记录一下我新配置环境的步骤.以备查看参考. sudo apt-get install gitgit clone https://github.com/gmarik/ ...

  6. mysql命令化操作实用小技巧

    ★1.问:如果我的mysql数据库服务器程序在D:\program files\phpstudy\mysql,里,那么我该怎么在cmd命令状态下使用它?      进入cmd状态后,系统默认在当前用户 ...

  7. Linux下安装mysql数据库

    l 检查是否已安装mysql的相关包 [root@localhost ~]# rpm -qa|grep -i mysql MySQL-server-5.6.19-1.el6.x86_64 MySQL- ...

  8. Spring声明式事务管理基于@Transactional注解

    概述:我们已知道Spring声明式事务管理有两种常用的方式,一种是基于tx/aop命名空间的xml配置文件,另一种则是基于@Transactional 注解.         第一种方式我已在上文为大 ...

  9. week 4 日志

    周一 上上个星期感冒,上个星期看完奇幻森林后痔疮发作,打了整整一礼拜的针,有点背.. 今天看了 css知多少(6)——选择器的优先级 http://www.cnblogs.com/wangfupeng ...

  10. 最少知识原则(Least Knowledge Principle)

    最少知识原则(Least Knowledge Principle),或者称迪米特法则(Law of Demeter),是一种面向对象程序设计的指导原则,它描述了一种保持代码松耦合的策略.其可简单的归纳 ...