Node.js中的进程与线程

更新日期: 2021-05-20阅读: 1.2k标签: 线程

1. 回顾进程和线程的定义

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。

线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。


2. Node.js的单线程

Node特点主线程是单线程的 一个进程只开一个主线程,基于事件驱动的、异步非阻塞I/O,可以应用于高并发场景。

Nodejs中没有多线程,为了充分利用多核cpu,可以使用子进程实现内核的负载均衡,那我们就要解决以下问题:

Node.js 做耗时的计算时候阻塞问题。

Node.js如何开启多进程。

开发过程中如何实现进程守护。


3. 场景实例

const http = require('http');
http.createServer((req,res)=>{
    if(req.url === '/sum'){ // 求和
        let sum = 0;
        for(let i = 0 ; i < 10000000000 ;i++){
            sum+=i;
        }
        res.end(sum+'')
    }else{
        res.end('end');
    }
}).listen(3000);
// 这里我们先访问/sum,在新建一个浏览器页卡访问/ 
// 会发现要等待/sum路径处理后才能处理/路径 


4. 开启进程

Node.js 进程创建,是通过child_process模块实现的:

child_process.spawn() 异步生成子进程。

child_process.fork() 产生一个新的Node.js进程,并使用建立的IPC通信通道调用指定的模块,该通道允许在父级和子级之间发送消息。

child_process.exec() 产生一个shell并在该shell中运行命令。

child_process.execFile() 无需产生shell。

4.1. spawn

spawn产卵,可以通过此方法创建一个子进程:

let { spawn } = require("child_process");
let path = require("path");
// 通过node命令执行sub_process.js文件
let childProcess = spawn("node",['sub_process.js'], {
  cwd: path.resolve(__dirname, "test"), // 找文件的目录是test目录下
  stdio: [0, 1, 2] 
});
// 监控错误
childProcess.on("error", function(err) {
  console.log(err);
});
// 监听关闭事件
childProcess.on("close", function() {
  console.log("close");
});
// 监听退出事件
childProcess.on("exit", function() {
  console.log("exit");
});

stido这个属性非常有特色,这里我们给了0,1,2这三个值分别对应住进程的process.stdin,process.stdout和process.stderr这代表着主进程和子进程共享标准输入和输出:

let childProcess = spawn("node",['sub_process.js'], {
  cwd: path.resolve(__dirname, "test"), // 找文件的目录是test目录下
  stdio: [0, 1, 2] 
});

可以在当前进程下打印sub_process.js执行结果默认在不提供stdio参数时为stdio:['pipe'],也就是只能通过流的方式实现进程之间的通信:

let { spawn } = require("child_process");
let path = require("path");
// 通过node命令执行sub_process.js文件
let childProcess = spawn("node",['sub_process.js'], {
  cwd: path.resolve(__dirname, "test"),
  stdio:['pipe'] // 通过流的方式
});
// 子进程读取写入的数据
childProcess.stdout.on('data',function(data){
    console.log(data);
});
// 子进程像标准输出中写入
process.stdout.write('hello');

使用ipc方式通信,设置值为stdio:['pipe','pipe','pipe','ipc']可以通过on('message')和send方式进行通信:

let { spawn } = require("child_process");
let path = require("path");
// 通过node命令执行sub_process.js文件
let childProcess = spawn("node",['sub_process.js'], {
  cwd: path.resolve(__dirname, "test"),
  stdio:['pipe','pipe','pipe','ipc'] // 通过流的方式
});
// 监听消息
childProcess.on('message',function(data){
    console.log(data);
});
// 发送消息
process.send('hello');

还可以传入ignore进行忽略,传入inherit表示默认共享父进程的标准输入和输出。

产生独立进程:

let { spawn } = require("child_process");
let path = require("path");
// 通过node命令执行sub_process.js文件
let child = spawn('node',['sub_process.js'],{
    cwd:path.resolve(__dirname,'test'),
    stdio: 'ignore',
    detached:true // 独立的线程
});
child.unref(); // 放弃控制

4.2. fork

衍生新的进程,默认就可以通过ipc方式进行通信:

let { fork } = require("child_process");
let path = require("path");
// 通过node命令执行sub_process.js文件
let childProcess = fork('sub_process.js', {
  cwd: path.resolve(__dirname, "test"),
});
childProcess.on('message',function(data){
    console.log(data);
});

fork是基于spawn的,可以多传入一个silent属性来设置是否共享输入和输出。

fork原理:

function fork(filename,options){
    let stdio = ['inherit','inherit','inherit']
    if(options.silent){ // 如果是安静的  就忽略子进程的输入和输出
        stdio = ['ignore','ignore','ignore']
    }
    stdio.push('ipc'); // 默认支持ipc的方式
    options.stdio = stdio
    return spawn('node',[filename],options)
}

到了这里我们就可以解决“3.场景实例”中的场景实例了:

const http = require('http');
const {fork} = require('child_process');
const path = require('path');
http.createServer((req,res)=>{
    if(req.url === '/sum'){
        let childProcess = fork('calc.js',{
            cwd:path.resolve(__dirname,'test')
        });
        childProcess.on('message',function(data){
            res.end(data+'');
        })
    }else{
        res.end('ok');
    }
}).listen(3000);

4.3. execFile

通过node指令,直接执行某个文件:

let childProcess = execFile("node",['./test/sub_process'],function(err,stdout,stdin){
    console.log(stdout); 
});

内部调用的是 spawn方法。

4.4. exec

let childProcess = exec("node './test/sub_process'",function(err,stdout,stdin){
    console.log(stdout)
});

内部调用的是execFile,其实以上三个方法都是基于spawn的。


5. cluster

Node.js的单个实例在单个线程中运行。为了利用多核系统,用户有时会希望启动Node.js进程集群来处理负载。自己通过进程来实现集群。

子进程与父进程共享HTTP服务器 fork实现:

let http = require('http');
let {
    fork
} = require('child_process');
let fs = require('fs');
let net = require('net');
let path = require('path');
let child = fork(path.join(__dirname, '8.child.js'));
let server = net.createServer();
server.listen(8080, '127.0.0.1', function () {
    child.send('server', server);
    console.log('父进程中的服务器已经创建');
    let httpServer = http.createServer();
    httpServer.on('request', function (req, res) {
        if (req.url != '/favicon.ico') {
            let sum = 0;
            for (let i = 0; i < 100000; i++) {
                sum += 1;
            }
            res.write('客户端请求在父进程中被处理。');
            res.end('sum=' + sum);
        }
    });
    httpServer.listen(server);
});

let http = require('http');
process.on('message', function (msg, server) {
    if (msg == 'server') {
        console.log('子进程中的服务器已经被创建');
        let httpServer = http.createServer();
        httpServer.on('request', function (req, res) {
            if (req.url != '/favicon.ico') {
                sum = 0;
                for (let i = 0; i < 10000; i++) {
                    sum += i;
                }
                res.write('客户端请求在子进程中被处理');
                res.end('sum=' + sum);
            }
        });
        httpServer.listen(server);
    }
});

进程与父进程共享socket对象:

let {
    fork
} = require('child_process');
let path = require('path');
let child = fork(path.join(__dirname, '11.socket.js'));
let server = require('net').createServer();
server.on('connection', function (socket) {
    if (Date.now() % 2 == 0) {
        child.send('socket', socket);
    } else {
        socket.end('客户端请求被父进程处理!');
    }
});
server.listen(41234, );

process.on('message', function (m, socket) {
    if (m === 'socket') {
        socket.end('客户端请求被子进程处理.');
    }
});

使用cluster模块更加方便:

let cluster = require("cluster");
let http = require("http");
let cpus = require("os").cpus().length;
const workers = {};
if (cluster.isMaster) {
    cluster.on('exit',function(worker){
        console.log(worker.process.pid,'death')
        let w = cluster.fork();
        workers[w.pid] = w;
    })
  for (let i = 0; i < cpus; i++) {
    let worker = cluster.fork();
    workers[worker.pid] = worker;
  }
} else {
  http
    .createServer((req, res) => {
      res.end(process.pid+'','pid');
    })
    .listen(3000);
  console.log("server start",process.pid);
}

原文链接:https://mp.weixin.qq.com/s/CRAptkRRCWunLgwjA6BFrQ
来自: 腾讯IMWeb前端团队 


链接: https://www.fly63.com/article/detial/10303

理解的线程、进程的关系与区别

进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,进程包含多个线程在运行。一简言之: 进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,进程包含多个线程在运行。

javascript中的伪线程,使用setTimeout模拟一个多线程

浏览器的内核是多线程的,一个浏览器一般至少实现三个常驻线程:javascript引擎线程,GUI渲染线程,浏览器事件触发线程。当我们要循环过百万级的数据甚至亿的时候怎么办?那就用setTimeout模拟一个多线程。

聊聊 JavaScript 与浏览器的那些事 - 引擎与线程

对 JavaScript 解释器和浏览器的线程机制理解的不是特别透彻,很容易混淆浏览器多线程机制并错误认为由于 Web Worker 的设计使得 JavaScript 拥有了多线程的能力。事后搜了不少资料进行学习,整理成此文,主要介绍浏览器的各个引擎、线程间的工作机制以及 JavaScript 单线程的一些事。

浏览器进程线程

进程是正在运行的程序的实例;线程(英语:thread)是操作系统能够进行运算调度的最小单位。可以打开任务管理器,可以看到有一个后台进程列表。这里就是查看进程的地方,而且可以看到每个进程的内存资源信息以及cpu占有率。

JavaScript多线程编程

浏览器端JavaScript是以单线程的方式执行的,也就是说JavaScript和UI渲染占用同一个主线程,那就意味着,如果JavaScript进行高负载的数据处理,UI渲染就很有可能被阻断,浏览器就会出现卡顿,降低了用户体验。

Node.js 多线程完全指南

很多人都想知道单线程的 Node.js 怎么能与多线程后端竞争。考虑到其所谓的单线程特性,许多大公司选择 Node 作为其后端似乎违反直觉。要想知道原因,必须理解其单线程的真正含义。

JavaScript Event Loop和微任务、宏任务

JavaScript的一大特点就是单线程, 同一时间只能做一件事情,主要和它的用途有关, JavaScript主要是控制和用户的交互以及操作DOM。注定它是单线程。 假如是多个线程, 一个移除DOM节点,一个新增DOM节点,浏览器以谁的为准呢?

如何理解JS的单线程?

JS本质是单线程的。也就是说,它并不能像JAVA语言那样,两个线程并发执行。 但我们平时看到的JS,分明是可以同时运作很多任务的,这又是怎么回事呢?

理解JS执行顺序

众所周知,JS的执行顺序是自上而下的。 严格意义上来说,javascript没有多线程的概念,所有的程序都是单线程依次执行的。 就是代码在执行过程中,另一段代码想要执行就必须等当前代码执行完成后才可以进行。

初始WebWorker

JS单线程:我们都知道JavaScript它是一个单线程的语言,同一时间只能做一件事。比如:在浏览器中,某一时刻我们在操作DOM,你们这个时刻我们就不能去运行JavaScript代码,反过来也是,当我们在运行JavaScript代码的时候

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!