直接上代码:
1
2
3
4
5
6
7
8
9
setTimeout(function () {
for (var i = 0 ; i < 10000000000 ; i++) {
}
}, 200 );
setTimeout(function () {
console .log('210 ms...' );
}, 210 );
这个小例子很经典的解释了Node.js遇到密集型CPU的时候问题,这个程序分别在两个时间点触发,虽然在210ms得时候回调内得程序执行非常很快,但是在200ms得时候处理了一个CPU非常密集型的任务就导致整个线程阻塞了. 至于Event Loop机制可以参考:http://www.infoq.com/cn/articles/nodejs-weakness-cpu-intensive-tasks 这篇文章中关于Event Loop和Tick 的一段:
每个Node程序的主线程都有一个event loop,JavaScript代码全在这个单线程下运行。所有的I/O操作以及对本地API的调用,或者是异步的(借助程序所在平台的机制),或者运行在另外的线程中。这全都是通过libuv处理的。所以当socket上有数据过来,或本地API函数返回时,需要有种同步的方式调用对刚发生的这一特定事件感兴趣的JavaScript函数。
在发生事件的线程中直接调用JS函数是不安全的,因为那样也会遇到常规多线程程序遇到的问题,竞态条件、非原子操作的内存访问等等。所以要以一种线程安全的方式把事件放在队列中,如果写成代码,大致应该是这样的:
1
2
3
lock (queue ) {
queue .push(event);
}
然后在执行JavaScript的主线程中(即event loop的c代码):
1
2
3
4
5
6
7
8
9
10
11
12
while (true ) {
lock (queue ) {
var tickEvents = copy(queue );
queue .empty();
}
for (var i = 0 ; i < tickEvents.length; i++) {
InvokeJSFunction(tickEvents[i]);
}
}
while (true) (在真正的node源码中并不是这样的;这里只是为了说明)表示event loop。里面的for为队列中的每个事件调用JS函数。Event loop在每个tick中都会调用与外部事件相关联的零个或多个回调函数,一旦队列被清空,并且最后一个函数返回后,tick就结束了。然后回到开始(下一个tick),重新开始检查其它线程在JavaScript运行时加到队列中的事件。
那么这个队列中的东西都是谁放进来的呢?
1
2
3
4
5
process.nextTick
setTimeout/setInterval
I /O (来自fs、net等)
crypto中的CPU密集型函数,比如crypto streams、pbkdf2和PRNG
所有使用libuv工作队列异步调用C /C ++库的本地模块
同样下面代码当调用t1接口时也会导致t2的接口变慢.
1
2
3
4
5
6
7
8
9
app.get("/t1" , function * (next) {
for (var i=0 ; i<5000000000 ; i++) {
}
console .log("t1" );
});
app.get("/t2" , function * (next) {
console .log("t2" );
});
这么看Node.js是否能够胜任cpu密集型操作呢,答案当然是否定的,Node.js虽然是单线程,但是可以开启多个Node.js实例来充分利用多核的优势,另外Node.js还支持子进程,通过子进程来计算… for.js
1
2
3
4
5
6
7
8
9
10
11
12
13
var calc = function () {
for (var i = 0 ; i < 10000000000 ; i++) {
}
}
process.on('message' , function (m) {
console .log("recv mesage" );
calc();
process.send(1 );
});
process.on('SIGHUP' , function () {
process.exit();
});
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var fork = require ('child_process' ).fork;
setTimeout(function () {
var worker = fork("./for.js" );
worker.on("message" , function (m) {
console .log("world" );
worker.kill();
});
worker.send(1 );
}, 200 );
setTimeout(function () {
console .log('hello' );
}, 210 );
上面这个程序就是利用进程间来通信,for.js是子进程执行的密集型计算.main.js可以继续调度其他程序.充分利用了cpu.
(转载文章请注明原文出处 MORE THAN VIMER)
网友评论已有0条评论, 我也要评论