Node.js的进程与线程
一、单线程的优缺点
1、php模型
2、Node.js模型
3、Node.js特点
- 高性能 避免频繁创建、切换线程的开销,使执行速度更加迅速;资源占用小,无需为每个请求创建线程的模型从而占用内存;
- 线程安全 无需担心同一个变量同时被多个线程进行读/写而造成程序崩溃;免去了多线程编程中忘记对变量加锁或者解锁造成的悲剧;
- 异步和非堵塞 Node.js在底层访问I/O是多线程的;
- 堵塞的单线程 当需要大量计算时,其他请求便会等待延迟执行;
- 单线程与多核 线程是CPU调度的基本单位,一个CPU同时只能执行一个线程的任务;如果运行Node.js的机器是想i5、i7这样的多核CPU,则无法充分利用多核CPU的性能来为Node.js服务;
二、多线程
1、tagg2模块
tagg2让Node.js支持多线程的开发,通过tagg2创建子线程,将计算量大的部分丢入了子线程中进行,保证了Node.js主线程的舒畅,当子线程任务执行完毕将会执行主线程的回调函数。
var express = require('express');
var tagg2 = require("tagg2");
var app = express();
var th_func = function(){//线程执行函数,以下内容会在线程中执行
var fibo =function fibo (n) {//在子线程中定义fibo函数
return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
}
var n = fibo(~~thread.buffer);//执行fibo递归
thread.end(n);//当线程执行完毕,执行thread.end带上计算结果回调主线程
};
app.get('/', function(req, res){
var n = ~~req.query.n || 1;//获取用户请求参数
var buf = new Buffer(n.toString());
tagg2.create(th_func, {buffer:buf}, function(err,result){
//创建一个js线程,传入工作函数,buffer参数以及回调函数
if(err) return res.end(err);//如果线程创建失败
res.end(result.toString());//响应线程执行计算的结果
})
});
app.listen(8124);
2、v8引擎
tagg2包是利用phtread库和v8的v8::Isolate Class类来实现js多线程功能的。
Isolate代表着一个独立的v8引擎实例,v8的Isolate拥有完全分开的状态,在一个Isolate实例中的对象不能够在另外一个Isolate实例中使用。嵌入式开发者可以在其他线程创建一些额外的Isolate实例并行运行。在任何时刻,一个Isolate实例只能够被一个线程进行访问,可以利用加锁/解锁进行同步操作。
因此,我们在进行v8的嵌入式开发时,无法在多线程中访问js变量,这条规则将直接导致我们之前的tagg2里面线程执行的函数无法使用Node.js的核心api,比如fs,crypto等模块。
如此看来,tagg2包还是有它使用的局限性,针对一些可以使用js原生的大量计算或循环可以使用tagg2,Node.js核心api因为无法从主线程共享对象的关系,也就不能跨线程使用了。
3、libuv
libuv是一个跨平台的异步I/O库,它主要用于Node.js的开发,主要包括了Event loops事件循环,Filesystem文件系统,Networking网络支持,Threads线程,Processes进程,Utilities其他工具。
在Node.js核心api中的异步多线程大多是使用
libuv来实现的。
二、多进程
在支持html5的浏览器里,我们可以使用webworker来将一些耗时的计算丢入worker进程中执行,这样主进程就不会阻塞,用户也就不会有卡顿的感觉了。
在Node.js中利用Node.js核心api里的child_process模块也可以实现webworker的效果。child_process模块提供了fork的方法,可以启动一个Node.js文件,将它作为worker进程,当worker进程工作完毕,把结果通过send方法传递给主进程,然后自动退出,这样我们就利用了多进程来解决主线程阻塞的问题。
- app.js
var express = require('express');
var fork = require('child_process').fork;
var app = express();
app.get('/', function(req, res){
var worker = fork('./work_fibo.js') //创建一个工作进程
worker.on('message', function(m) {//接收工作进程计算结果
if('object' === typeof m && m.type === 'fibo'){
worker.kill();//发送杀死进程的信号
res.send(m.result.toString());//将结果返回客户端
}
});
worker.send({type:'fibo',num:~~req.query.n || 1});
//发送给工作进程计算fibo的数量
});
app.listen(8124);
- work_fibo.js
var fibo = function fibo (n) {//定义算法
return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
}
process.on('message', function(m) {
//接收主进程发送过来的消息
if(typeof m === 'object' && m.type === 'fibo'){
var num = fibo(~~m.num);
//计算jibo
process.send({type: 'fibo',result:num})
//计算完毕返回结果
}
});
process.on('SIGHUP', function() {
process.exit();//收到kill信息,进程退出
});
主线程的
kill方法并不是真的使子进程退出,而是会触发子进程的SIGHUP事件,真正的退出还是依靠process.exit()。