• Node.js中的进程与线程


    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的。

    https://www.98891.com/article-88-1.html

    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);
    }

  • 相关阅读:
    Tomcat通过脚本自动部署
    【转】调用百度API,HTML在线文字转语音播报
    vim 多行注释消除注释,多行删除
    sublime快捷键
    引号-下划线,连接多个变量
    图片压缩工具optipng/jpegoptim安装
    netsh-winsock-reset;ping的通公网IP和DNS地址和内网网关,就是不能解析域名;
    mysql简单性能排查
    nginx-upstream-keepalive;accept_mutex-proxy_http_version-1.1-proxy_set_header-connection
    icmp,tcp,traceroute,ping,iptables
  • 原文地址:https://www.cnblogs.com/qianxiaox/p/14933988.html
Copyright © 2020-2023  润新知