JavaScript的线程与进程的一些了解

in Notes with 0 comment

本文主要探讨一下JavaScript在单线程下的问题以及它的解决方法,围绕浏览器与Node.js这两个点出发,内容可能不严谨,会有错误,请指出来···

Web Workers

众所周知,JavaScript 在浏览器里是以单线程的方式运行。

单线程最大的好处是程序状态单一,不用像多线程编程那样处处在意状态的同步问题,没有死锁的存在,也没有线程上下文交换所带来的性能上的开销。当然弱点也很明显,例如无法利用多核CPU,错误引起整个应用退出和计算占用CPU导致无法继续工作。

针对单线程下的大计算量问题,W3C 制定的 HTML5 标准里 的 WebWorkers 就是解决这个问题。这就允许了 JavaScript 创建多个线程,子线程受主线程控制,将计算工作交付给子线程,然后通过消息传递的方式传递结果。

例如:

var worker = new Worker('worker.js');
//接收worker传递过来的数据
worker.onmessage = function(event){
  console.log(event.data);
};
// woker.js
var i = 0;
function timedCount(){
    for(var j = 0, sum = 0; j < 100; j++){
        for(var i = 0; i < 100000000; i++){
            sum+=i;
        };
    };
    //将得到的sum发送回主线程
    postMessage(sum);
};
//将执行timedCount前的时间,通过postMessage发送回主线程
postMessage('Before computing, '+new Date());
timedCount();
//结束timedCount后,将结束时间发送回主线程
postMessage('After computing, ' +new Date());

Node.js 进程

JavaScript 在单线程上,一旦单线程出现异常,且没有被捕获,将会引起整个进程崩溃。Node.js采用与 WebWorkers 相同的思路来解决单线程中大计算量的问题,那就是子进程。通过创建子进程,一来可以实现充分利用多核 CPU,二来解决单线程的健壮性的问题。通过将计算任务分派到各个子进程中,将大量计算分解掉,然后再通过进程之间的事件消息来传递结果。

例如:

// master.js
var fork = require('child_process').fork;
var cpuCount = require('os').cpus().length;

for(var i = 0; i < cpuCount; i++){
  fork('./worker.js');
}
// worker.js
var http = require('http');
http.createServer(function(req,res){
  res.writeHead(200,{'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(Math.round((1 + Math.random()) * 1000),'127.0.0.1');

通过 node master.js 启动,在*nix系统下,输入ps aux|grep worker.js命令行查看进程的数量,如下

chakhsu          44764   0.0  0.3  3033512  25496 s000  S+   11:12下午   0:00.12 /usr/local/bin/node ./worker.js
chakhsu          44763   0.0  0.3  3033512  25436 s000  S+   11:12下午   0:00.12 /usr/local/bin/node ./worker.js
chakhsu          44762   0.0  0.3  3024296  25192 s000  S+   11:12下午   0:00.11 /usr/local/bin/node ./worker.js
chakhsu          44761   0.0  0.3  3033512  25564 s000  S+   11:12下午   0:00.12 /usr/local/bin/node ./worker.js

这就是 Master-Worker 模型,又称为主从模式。该模式下,进程分为两种,主进程和工作进程,其中主进程不负责具体的业务处理,而是负责调度或管理工作进程,它是趋向于稳定的;工作进程负责具体的业务,因为业务的特殊性与多样性,所以开发者需要注意工作进程的稳定性。

这里fork()复制出来的进程都是独立的进程,每个进程有着独立而全新的V8实例,fork行为是昂贵的,这里启动多进程只是为了充分将cpu利用起来,并发问题则是通过事件驱动的方式在单线程上解决了。

内容不多,大概就这样

Responses