《Node.js实战》读书笔记二

341 阅读4分钟

Node.js的进程与线程

一、单线程的优缺点

1、php模型

image.png

2、Node.js模型

image.png

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库和v8v8::Isolate Class类来实现js多线程功能的。

Isolate代表着一个独立的v8引擎实例,v8Isolate拥有完全分开的状态,在一个Isolate实例中的对象不能够在另外一个Isolate实例中使用。嵌入式开发者可以在其他线程创建一些额外的Isolate实例并行运行。在任何时刻,一个Isolate实例只能够被一个线程进行访问,可以利用加锁/解锁进行同步操作。

因此,我们在进行v8的嵌入式开发时,无法在多线程中访问js变量,这条规则将直接导致我们之前的tagg2里面线程执行的函数无法使用Node.js的核心api,比如fscrypto等模块。

如此看来,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()