Node.js中的进程与线程

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

 [[400868]]

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

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

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

2. Node.js的单线程

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

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

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

  • Node.js如何开启多进程。

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

3. 场景实例

  1. 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 产卵,可以通过此方法创建一个子进程:

  1. 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"); });  
  2. stido 这个属性非常有特色,这里我们给了012这三个值分别对应住进程的 process.stdin , process.stdout 和 process.stderr 这代表着主进程和子进程共享标准输入和输出: 

 

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

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

  1. 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 方式进行通信:

  1. 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 表示默认共享父进程的标准输入和输出。

产生独立进程:

  1. 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 方式进行通信:

  1. 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原理:

  1. 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.场景实例”中的场景实例了:

  1. 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 指令,直接执行某个文件:

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

内部调用的是  spawn 方法。

4.4. exec

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

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

5. cluster

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

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

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

 

  1. 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对象:

  1. 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, );  

 

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

使用cluster模块更加方便:

  1. let cluster = require("cluster"); let http = require("http"); let cpus = require("os").cpus(). 

 

责任编辑:张燕妮 来源: 腾讯IMWeb前端团队
相关推荐

2021-04-20 12:39:52

Node.js多线程多进程

2021-08-04 23:30:28

Node.js开发线程

2019-08-15 14:42:24

进程线程javascript

2019-03-29 16:40:02

Node.js多线程前端

2022-06-23 06:34:56

Node.js子线程

2021-03-09 08:03:21

Node.js 线程JavaScript

2022-10-28 15:51:24

JavaScript开发Node.js

2016-08-11 14:02:02

NodeJS前端

2024-01-05 08:49:15

Node.js异步编程

2020-04-15 15:48:03

Node.jsstream前端

2013-11-01 09:34:56

Node.js技术

2015-03-10 10:59:18

Node.js开发指南基础介绍

2022-01-29 22:27:31

内核子线程应用

2017-03-20 13:43:51

Node.js内存泄漏

2017-03-19 16:40:28

漏洞Node.js内存泄漏

2011-09-08 10:32:27

Node.js

2020-05-29 15:33:28

Node.js框架JavaScript

2021-12-25 22:29:57

Node.js 微任务处理事件循环

2012-02-03 09:25:39

Node.js

2021-03-04 23:12:57

Node.js异步迭代器开发
点赞
收藏

51CTO技术栈公众号