Node.js的异步I/O
来源:互联网 发布:移动网络玩游戏卡吗 编辑:程序博客网 时间:2024/05/16 13:04
本文为读书笔记。
一、Node.js异步I/O的实现原理
1.事件循环
在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环体的过程我们称为Tick。每个Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调函数。如果存在关联的回调函数,就执行它们。然后进入下个循环,如果不再有事件处理,就退出进程。流程图如图3-11所示。
2.观察者
在Windows下,这个循环基于IOCP创建,而在*nix下则基于多线程创建。
3.请求对象
对于一般的(非异步)回调函数,函数由我们自行调用,如下所示:
var forEach = function (list, callback) {
for (var i = 0; i < list.length; i++) {
callback(list[i], i, list);
}
};
对于Node中的异步I/O调用而言,回调函数却不由开发者来调用。那么从我们发出调用后,到回调函数被执行,中间发生了什么呢?
下面我们以最简单的fs.open()方法来作为例子,探索Node与底层之间是如何执行异步I/O调用以及回调函数究竟是如何被调用执行的:
fs.open = function(path, flags, mode, callback) {
// ...
binding.open(pathModule._makeLong(path),
stringToFlags(flags),
mode,
callback);
};
req_wrap->object_->Set(oncomplete_sym, callback);
对象包装完毕后,在Windows下,则调用QueueUserWorkItem()方法将这个FSReqWrap对象推入线程池中等待执行,该方法的代码如下所示:
QueueUserWorkItem(&uv_fs_thread_proc, req, WT_EXECUTEDEFAULT)
4.执行回调
线程池中的I/O操作调用完毕之后,会将获取的结果储存在req->result属性上,然后调用PostQueuedCompletionStatus()通知IOCP,告知当前对象操作已经完成。PostQueuedCompletionStatus()方法的作用是向IOCP提交执行状态,并将线程归还线程池。通过PostQueuedCompletionStatus()方法提交的状态,可以通过GetQueuedCompletionStatus()提取。
在这个过程中,我们其实还动用了事件循环的I/O观察者。在每次Tick的执行中,它会调用IOCP相关的GetQueuedCompletionStatus()方法检查线程池中是否有执行完的请求,如果存在,会将请求对象加入到I/O观察者的队列中,然后将其当做事件处理。
I/O观察者回调函数的行为就是取出请求对象的result属性作为参数,取出oncomplete_sym属性作为方法,然后调用执行,以此达到调用JavaScript中传入的回调函数的目的。
至此,整个异步I/O的流程完全结束,如图3-13所示。
5.总结
异步I/O的几个关键词:单线程、事件循环、观察者和I/O线程池。
这里单线程与I/O线程池之间看起来有些悖论的样子。由于我们知道JavaScript是单线程的,所以按常识很容易理解为它不能充分利用多核CPU。事实上,在Node中,除了JavaScript是单线程外,Node自身其实是多线程的,只是I/O线程使用的CPU较少。另一个需要重视的观点则是,除了用户代码无法并行执行外,所有的I/O(磁盘I/O和网络I/O等)则是可以并行起来的。
二、非I/O的异步API
1.定时器
定时器的问题在于,它并非精确的(在容忍范围内)。尽管事件循环十分快,但是如果某一次循环占用的时间较多,那么下次循环时,它也许已经超时很久了。譬如通过setTimeout()设定一个任务在10毫秒后执行,但是在9毫秒后,有一个任务占用了5毫秒的CPU时间片,再次轮到定时器执行时,时间就已经过期4毫秒。
2.process.nextTick()
// on the way out, don't bother.
// it won't get fired anyway
if (process._exiting) return;
if (tickDepth >= process.maxTickDepth)
maxTickWarn();
var tock = { callback: callback };
if (process.domain) tock.domain = process.domain;
nextTickQueue.push(tock);
if (nextTickQueue.length) {
process._needTickCallback();
}
};
每次调用process.nextTick()方法,只会将回调函数放入队列中,在下一轮Tick时取出执行。
3.setImmediate()
在具体实现上,process.nextTick()的回调函数保存在一个数组中,setImmediate()的结果则是保存在链表中。在行为上,process.nextTick()在每轮循环中会将数组中的回调函数全部执行完,而setImmediate()在每轮循环中执行链表中的一个回调函数。
process.nextTick(function () {
console.log('nextTick延迟执行1');
});
process.nextTick(function () {
console.log('nextTick延迟执行2');
});
// 加入两个setImmediate()的回调函数
setImmediate(function () {
console.log('setImmediate延迟执行1');
// 进入下次循环
process.nextTick(function () {
console.log('强势插入');
});
});
setImmediate(function () {
console.log('setImmediate延迟执行2');
});
console.log('正常执行');
其执行结果如下:
正常执行
nextTick延迟执行1
nextTick延迟执行2
setImmediate延迟执行1
强势插入
setImmediate延迟执行2
从执行结果上可以看出,当第一个setImmediate()的回调函数执行后,并没有立即执行第二个,而是进入了下一轮循环,再次按process.nextTick()优先、setImmediate()次后的顺序执行。
三、事件驱动与高性能服务器
同步式
每进程/每请求
每线程/每请求
- Node.js的异步I/O
- Node.js的异步I/O
- node.js 异步I/O
- Node.js 异步I/O
- Node的异步 I/O
- node的异步I/O
- 理解Node.js的异步非阻塞I/O模型
- Node.js的单线程异步I/O优势
- 浅解Node.js的异步非阻塞I/O模型
- 深入浅出Node.js(五):初探Node.js的异步I/O实现
- 深入浅出Node.js(五):初探Node.js的异步I/O实现
- 深入浅出Node.js(五):初探Node.js的异步I/O实现
- 深入浅出Node.js(五):初探Node.js的异步I/O实现
- 深入浅出Node.js(五):初探Node.js的异步I/O实现
- 深入浅出Node.js(五):初探Node.js的异步I/O实现
- 【深入浅出Node.js系列五】初探Node.js的异步I/O实现
- Node.js学习(5)----异步I/O和同步
- Node.js 异步式 I/O 与事件驱动
- openssl.cnf
- CSS3快速上手之7:线性渐变+各种分布
- 《深入理解计算机系统》第一章 计算机系统漫游
- 数据库驱动以及url
- 内存管理(3)
- Node.js的异步I/O
- 深入研究java.lang.Class类
- 局部内部类为什么只能访问final局部变量,对于成员变量却可以随便访问?
- Latex 之table
- 字符编码之Base64/32/16编码
- Linux 下创建Oracle表空间
- LeetCode 第十九题 Remove Nth Node From End of List
- Memcache常见问题集
- Unix环境高级编程<一>:文件I-O之读写、偏移操作