2

node - 非阻塞的异步 IO

每当我们提起 node.js 时总会脱口而出 事件驱动非阻塞I/O单线程,所以我总结了以下几点对这三项概念的阐述,不一定正确仅仅代表个人观点。

单线程

当一个应用程序运行时会产生一个主进程,它与其他并行执行的应用程序一起竟争计算机系统资源,是管理和分配现有所占据资源的基本单位。每一个进程都有一个自己的地址空间(进程空间)。而线程是进程的一部分,二者相扶相依,其中单线程被称为轻权进程或轻量级进程,执行特性:

  1. 线程只有 3 个基本状态:就绪,执行,阻塞。

  2. 线程存在 5 种基本操作来切换线程的状态:派生,阻塞,激活,调度,结束。

下面这个图片简单解释了单线程的运作:

图片描述

用 php、apache、nginx 和 node.js 做对比是因为这四个应用恰好代表四种情况,其中 php 是典型的多线程的语言,apache 则是同步多进程的代表,也就是说连接每一个连接 apache 都对应一个子进程,而 nginx 是异步多线程,近万个连接对应一个子进程。

线程与非阻塞IO

当我们说 node.js 是单线程应用的时候,实际上这种单线程只是代码执行主程序上的单线程,在涉及到 IO 操作时仍然是多线程,下面我们看一段代码:

var path = require('path'),
    fs   = require('fs');

var i = 0;
console.time('fs.read');
fs.read(fs.openSync(path.join(__dirname, 'example.log'), 'r'), 10000, 0, 'utf-8', function () {
    console.log('1');
    console.timeEnd('fs.read');
});
console.log('2');
function test(cb) {
    console.time('test');
    console.log('3');
    while (i < 300000000) {
        i++
    }
    cb()
}
console.log('4');
test(function () {
    console.log('5');
    console.timeEnd('test');
});
console.log('6');
//process.exit();

运行结果时:

图片描述

数字标识分别为2、4、3、5、6、1,fstest 同样是回调函数一个立即执行并顺序执行,而另一个延迟执行,这显然不符合传统的程序执行顺序,也不符合有些人说的: 这种以定义当前感兴趣事件发生时由系统调用的函数来取代应用返回值的编程风格被称为事件驱动编程或者异步编程,这句话的错误在于是事件驱动编程 但不一定是异步编程异步编程 的先决条件是当前执行函数正在进行 IO 操作。

如果我们把上述代码的最后一句 //process.exit(); 的注释拿掉,可以看到执行结果:

图片描述

这种情况是因为 fs 的 IO 操作是异步,并且执行结果在 事件驱动编程事件队列 的最低端,而在它之前 process.exit(); 已经在 事件循环 过程中被从事件队列中取出放入调用堆栈,结束了主进程。所以发生 fs 的回调结果没有显示,因为它已经被放在了整段代码执行环境中的 事件队列 的最下方(这里就是非阻塞的实例)。

以上所述证明了 IO 操作与其他函数的这种区别是由 libeio 实现,libeio 是用多线程的方式,在标准的阻塞式IO上模拟非阻塞异步,线程池默认限制四线程。

图片描述

另外的 libev 事件可得到 IO 执行状态。Node.js 的开发者在 libev 和 libeio 的基础上还抽象出了 libuv 层: (http://docs.libuv.org/en/v1.x/design.html)
所有的 IO操作都会转发给由 libuv 管理的工作线程去执行,由 libuv 与 libev 和 libeio 进行交互。

事件驱动

事件驱动与事件循环互为犄角,其中事件循环具备两个功能:

  • 事件检测

  • 事件触发处理


kangkk
1.7k 声望69 粉丝

没什么想说的。