返回顶部
首页 > 资讯 > 前端开发 > JavaScript >Node多进程的实现方法
  • 891
分享到

Node多进程的实现方法

Node 多进程Node 多进程通信 2022-11-13 14:11:50 891人浏览 八月长安
摘要

目录1. child_process 模块2. spawn3. fork4. exec和execFile5. 各方法之间的比较5.1 spawn和execFile5.2 execFi

我们现在已经知道了node是单线程运行的,这表示潜在的错误有可能导致线程崩溃,然后进程也会随着退出,无法做到企业追求的稳定性;另一方面,单进程也无法充分多核CPU,这是对硬件本身的浪费。Node社区本身也意识到了这一问题,于是从0.1版本就提供了child_process模块,用来提供多进程的支持。

1. child_process 模块

child_process模块中包括了很多创建子进程的方法,包括forkspawnexecexecFile等等。它们的定义如下:

  • child_process.exec(command[, options][, callback])
  • child_process.spawn(command[, args][, options])
  • child_process.fork(modulePath[, args][, options])
  • child_process.execFile(file[, args][, options][, callback])

在这4个api中以spawn最为基础,因为其他三个API或多或少都是借助spawn实现的。

2. spawn

spawn方法的声明格式如下:

child_process.spawn(command[, args][, options])

spawn方法会使用指定的command来生成一个新进程,执行完对应的command后子进程会自动退出。

该命令返回一个child_process对象,这代表开发者可以通过监听事件来获得命令执行的结果。

下面我们使用spwan来执行ls命令:

const spawn = require('child_process').spawn;
const ls = spawn('ls', ['-1h', '/usr']);
ls.stdout.on('data', (data) => {
    console.log('stdout: ', daata.toString());
});
ls.stderr.on('data', (data) => {
    console.log('stderr: ', daata.toString());
});
ls.on('close', (code) => {
    console.log('child process exited with code', code);
});

其中spawn的第一个参数虽然是command,但实际接收的却是一个file,可以在linux或者Mac OSX上运行,这是由于ls命令也是以可执行文件形式存在的。

类似的,在windows系统下我们可以试着使用dir命令来实现功能类似的代码:

const spawn = require('child_process').spawn;
const ls = spawn('dir');
ls.stdout.on('data', (data) => {
    console.log('stdout: ', daata.toString());
});

然而在Windows下执行上面代码会出现形如Error:spawn dir ENOENT的错误。

原因就在于spawn实际接收的是一个文件名而非命令,正确的代码如下:

const spawn = require('child_process').spawn;
const ls = spawn('powershell', ['dir']);
ls.stdout.on('data', (data) => {
    console.log('stdout: ', daata.toString());
});

这个问题的原因与操作系统本身有关,在Linux中,一般都是文件,命令行的命令也不例外,例如ls命令是一个名为ls的可执行文件;而在Windows中并没有名为dir的可执行文件,需要通过cmd或者powershell之类的工具提供执行环境。

3. fork

在Linux环境下,创建一个新进程的本质是复制一个当前的进程,当用户调用 fork 后,操作系统会先为这个新进程分配空间,然后将父进程的数据原样复制一份过去,父进程和子进程只有少数值不同,例如进程标识符(PD)。

对于 Node 来说,父进程和子进程都有独立的内存空间和独立的 V8 实例,它们和父进程唯一的联系是用来进程间通信的 IPC Channel。

此外,Node中fork和 POSIX 系统调用的不同之处在于Node中的fork并不会复制父进程。

Node中的fork是上面提到的spawn的一种特例,前面也提到了Node中的fork并不会复制当前进程。多数情况下,fork接收的第一个参数是一个文件名,使用fork("xx.js")相当于在命令行下调用node xx.js,并且父进程和子进程之间可以通过process.send方法来进行通信。

下面我们来看一个简单的栗子:

// master.js 调用 fork 来创建一个子进程
const child_process = require('child_process');
const worker = child_process.fork('worker.js', ['args1']);
worker.on('exit', () => {
  console.log('child process exit');
});
worker.send({ msg: 'hello child' });
worker.on('message', msg => {
  console.log('from child: ', msg);
});
// worker.js
const begin = process.argv[2];
console.log('I am worker ' + begin);
process.on('message', msg => {
  console.log('from parent ', msg);
  process.exit();
});
process.send({ msg: 'hello parent' });

fork内部会通过spawn调用process.executePath,即Node的可执行文件地址来生成一个Node实例,然后再用这个实例来执行fork方法的modulePath参数。

输出结果为:

I am worker args1
from parent  { msg: 'hello child' }
from child:  { msg: 'hello parent' }
child process exit

4. exec和execFile

如果我们开发一种系统,那么对于不同的模块可能会用到不同的技术来实现,例如 WEB服务器使用 Node ,然后再使用 Java 的消息队列提供发布订阅服务,这种情况下通常使用进程间通信的方式来实现。

但有时开发者不希望使用这么复杂的方式,或者要调用的干脆是一个黑盒系统,即无法通过修改源码来进行来实现进程间通信,这时候往往采用折中的方式,例如通过 shell 来调用目标服务,然后再拿到对应的输出。

child_process提供了一个execFile方法,它的声明如下:

child_process.execFile(file, args, options, callback) 

说明:

file {String}要运行的程序的文件名

args {Array}字符串参数列表

options {Object}

  • cwd {String}子进程的当前工作目录
  • env {Object}环境变量键值对
  • encoding {String}编码(默认为 'utf8'
  • timeout {Number}超时(默认为 0)
  • maxBuffer {Number}缓冲区大小(默认为 200*1024)
  • killSignal {String}结束信号(默认为'SIGTERM'

callback {Function}进程结束时回调并带上输出

  • error {Error}
  • stdout {Buffer}
  • stderr {Buffer}
  • 返回:ChildProcess对象

可以看出,execfilespawn在形式上的主要区别在于execfile提供了一个回调函数,通过这个回调函数可以获得子进程的标准输出/错误流。

使用 shell 进行跨进程调用长久以来被认为是不稳定的,这大概源于人们对控制台不友好的交互体验的恐惧(输入命令后,很可能长时间看不到一个输出,尽管后台可能在一直运算,但在用户看来和死机无异)。

在 Linux下执行exec命令后,原有进程会被替换成新的进程,进而失去对新进程的控制,这代表着新进程的状态也没办法获取了,此外还有 shell 本身运行出现错误,或者因为各种原因出现长时间卡顿甚至失去响应等情况。

node.js 提供了比较好的解决方案,timeout解决了长时间卡顿的问题,stdoutstderr则提供了标准输出和错误输出,使得子进程的状态可以被获取。

5. 各方法之间的比较

5.1 spawn和execFile

为了更好地说明,我们先写一段简单的 C 语言代码,并将其命名为 example.c:

#include<stdio.h>
int main() {
    printf("%s", "Hello World!");
    return 5;
}

使用 GCc 编译该文件:

gcc example.c -o example

生成名为example的可执行文件,然后将这个可执行文件放到系统环境变量中,然后打开控制台,输入example,看到最后输出"Hello World"

确保这个可执行文件在任意路径下都能访问。

我们分别用spawnexecfile来调用example文件。

首先是spawn

const spawn = require('child_process').spawn;
const ls = spawn('example');
ls.stdout.on('data', (data) => {
    console.log('stdout: ', daata.toString());
});
ls.stderr.on('data', (data) => {
    console.log('stderr: ', daata.toString());
});
ls.on('close', (code) => {
    console.log('child process exited with code', code);
});

程序输出:

stdout: Hello World!
child process exited with code 5

程序正确打印出了Hello World,此外还可以看到example最后的return 5会被作为子进程结束的code被返回。

然后是execFile

const exec = require('child_process').exec;
const child = exec('example', (error, stdout, stderr) => {
    if (error) {
        throw error;
    }
    console.log(stdout);
});

同样打印出Hello World,可见除了调用形式不同,二者相差不大。

5.2 execFile 和 spawn

在子进程的信息交互方面,spawn使用了流式处理的方式,当子进程产生数据时,主进程可以通过监听事件来获取消息;而exec是将所有返回的信息放在stdout里面一次性返回的,也就是该方法的maxBuffer参数,当子进程的输出超过这个大小时,会产生一个错误。

此外,spawn有一个名为shell的参数:

其类型为一个布尔值或者字符串,如果这个值被设置为true,,就会启动一个 shell 来执行命令,这个 shell 在 UNIX上是 bin/sh,,在Windows上则是cmd.exe。

5.3 exec 和 execFile

exec在内部也是通过调用execFile来实现的,我们可以从源码中验证这一点,在早期的Node源码中,exec命令会根据当前环境来初始化一个 shell,,例如 cmd.exe 或者 bin/sh,然后在shell中调用作为参数的命令。

通常execFile的效率要高于exec,这是因为execFile没有启动一个 shell,而是直接调用 spawn来实现的。

6. 进程间通信

前面介绍的几个用于创建进程的方法,都是属于child_process的类方法,此外childProcess类继承了EventEmitter,在childProcess中引入事件给进程间通信带来很大的便利。

childProcess中定义了如下事件。

Event:'close':进程的输入输出流关闭时会触发该事件。

Event:'disconnect':通常childProcess.disconnect调用后会触发这一事件。

Event:'exit':进程退出时触发。

Event:'message':调用child_process.send会触发这一事件

Event:'error':该事件的触发分为几种情况:

  • 该进程无法创建子进程。
  • 该进程无法通过kill方法关闭。
  • 无法发送消息给子进程。

Event:'error'事件无法保证一定会被触发,因为可能会遇到一些极端情况,例如服务器断电等。

上面也提到,childProcess模块定义了send方法,用于进程间通信,该方法的声明如下:

child.send(message[, sendHandle[, options]][, callback])

通过send方法发送的消息,可以通过监听message事件来获取。

// master.js 父进程向子进程发送消息
const child_process = require('child_process');
const worker = child_process.fork('worker.js', ['args1']);
worker.on('exit', () => {
  console.log('child process exit');
});
worker.send({ msg: 'hello child' });
worker.on('message', msg => {
  console.log('from child: ', msg);
});
// worker.js 子进程接收父进程消息
const begin = process.argv[2];
console.log('I am worker ' + begin);
process.on('message', msg => {
  console.log('from parent ', msg);
  process.exit();
});
process.send({ msg: 'hello parent' });

send方法的第一个参数类型通常为一个JSON对象或者原始类型,第二个参数是一个句柄,该句柄可以是一个net.Socket或者net.Server对象。下面是一个例子:

//master.js 父进程发送一个 Socket 对象
const child = require('child_process').fork('worker.js');
// Open up the server object and send the handle.
const server = require('net').createServer();
server.on('connection', socket => {
  socket.end('handled by parent');
});
server.listen(1337, () => {
  child.send('server', server);
});
//worker.js 子进程接收 Socket 对象
process.on('message', (m, server) => {
  if (m === 'server') {
    server.on('connection', socket => {
      socket.end('handled by child');
    });
  }
});

7. Cluster

前面已经介绍了child_process的使用,child_process的一个重要使用场景是创建多进程服务来保证服务稳定运行。

为了统一 Node 创建多进程服务的方式,Node 在之后的版本中增加了Cluster模块,Cluster可以看作是做了封装的child_Process模块。

Cluster模块的一个显著优点是可以共享同一个socket连接,这代表可以使用Cluster模块实现简单的负载均衡

下面是Cluster的简单栗子:

const cluster = require('cluster');
const Http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
  console.log('Master process id is', process.pid);
  // Fork workers.
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  cluster.on('exit', (worker, code, signal) => {
    console.log('worker process died, id ', worker.process.pid);
  });
} else {
  // Worker 可以共享同一个 tcp 连接
  // 这里的例子是一个 http 服务器
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(8000);
  console.log('Worker started, process id', process.pid);
}

上面是使用Cluster模块的一个简单的例子,为了充分利用多核CPU,先调用OS模块的cpus()方法来获得CPU的核心数,假设主机装有两个 CPU,每个CPU有4个核,那么总核数就是8。

在上面的代码中,Cluster模块调用fork方法来创建子进程,该方法和child_process中的fork是同一个方法。

Cluster模块采用的是经典的主从模型,由master进程来管理所有的子进程,可以使用cluster.isMaster属性判断当前进程是master还是worker,其中主进程不负责具体的任务处理,其主要工作是负责调度和管理,上面的代码中,所有的子进程都监听8000端口。

通常情况下,如果多个 Node 进程监听同一个端口时会出现Error: listen EADDRINUS的错误,而Cluster模块能够让多个子进程监听同一个端口的原因是master进程内部启动了一个 TCP 服务器,而真正监听端口的只有这个服务器,当来自前端的请求触发服务器的connection事件后,master会将对应的socket句柄发送给子进程。

到此这篇关于Node多进程的实现方法的文章就介绍到这了,更多相关Node多进程内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Node多进程的实现方法

本文链接: https://lsjlt.com/news/172157.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Node多进程的实现方法
    目录1. child_process 模块2. spawn3. fork4. exec和execFile5. 各方法之间的比较5.1 spawn和execFile5.2 execFi...
    99+
    2022-11-13
    Node 多进程 Node 多进程通信
  • Node的多进程服务如何实现
    这篇文章主要介绍“Node的多进程服务如何实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Node的多进程服务如何实现”文章能帮助大家解决问题。我们现在已经知道了Node是单线程运行的,这表示潜在...
    99+
    2023-07-01
  • 深入了解Node的多进程服务实现
    目录1. child_process 模块2. spawn3. fork4. exec 和 execFile5. 各方法之间的比较5.1 spawn 和 execFile5.2 ex...
    99+
    2024-04-02
  • Python多进程实现的方法是什么
    Python多进程实现的方法有两种:使用multiprocessing模块和使用concurrent.futures模块。 使用m...
    99+
    2024-04-02
  • php如何实现多进程和关闭进程的方法
    本篇内容介绍了“php如何实现多进程和关闭进程的方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!php实现关闭进程的方法:首先创建一个PH...
    99+
    2023-06-07
  • PHP底层的多进程编程与实现方法
    抱歉,我无法为您提供关于 PHP 底层的多进程编程与实现方法的文章,这是因为 PHP 并不是一个适合多进程编程的语言。PHP 是一种用于网页开发的服务器端脚本语言,主要用于处理动态网页内容,而不是用于编写多线程或多进程的系统编程。如果您对其...
    99+
    2023-11-08
    多进程编程 实现方法 PHP底层
  • node的多进程和多线程是什么
    今天小编给大家分享一下node的多进程和多线程是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解...
    99+
    2024-04-02
  • node中进程通信的实现方式有哪些
    这篇文章主要介绍“node中进程通信的实现方式有哪些”,在日常操作中,相信很多人在node中进程通信的实现方式有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”node中进...
    99+
    2024-04-02
  • Node中的进程和线程怎么实现
    这篇文章主要介绍了Node中的进程和线程怎么实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Node中的进程和线程怎么实现文章都会有所收获,下面我们一起来看看吧。一、进程和线程1.1、专业性文字定义进程(Pr...
    99+
    2023-07-04
  • python实现多线程post方法进行压
    #coding=utf-8 import httplib,urllib from time import ctime import threading import csv postJson={ } #定义需要...
    99+
    2023-01-31
    多线程 方法 python
  • Linux BASH多进程并行处理的方法实现
    #!/bin/bash SEND_THREAD_NUM=13 tmp_fifofile="/tmp/$$.fifo" # 脚本运行的当前进程ID号作为文件名 mkfifo “$tmp_fifofile" ...
    99+
    2022-06-04
    进程 方法 Linux
  • Python进阶之多线程的实现方法总结
    目录线程Python中的多线程threading.Thread () 创建线程继承 threading.Thread 类的线程创建主线程使用daemon参数控制过程使用.join()...
    99+
    2023-05-16
    Python多线程实现方法 Python多线程实现 Python多线程
  • c++多进程通信实现的方法是什么
    C++多进程通信可以通过以下几种方法实现: 管道(Pipe):在父进程中创建管道,然后通过fork()系统调用创建子进程,父子进...
    99+
    2024-04-02
  • node连接redis的方法实现
    本篇文章介绍nodejs连接redis,以及redis的基本使用。 安装redis。 cnpm i redis -S 封装redis配置信息。 config/db.js // re...
    99+
    2023-02-27
    node连接redis node redis连接
  • Python实现多进程共享数据的方法分析
    本文实例讲述了Python实现多进程共享数据的方法。分享给大家供大家参考,具体如下: 示例一: # -*- coding:utf-8 -*- from multiprocessing import Pr...
    99+
    2022-06-04
    进程 方法 数据
  • shell脚本实现多进程运行的方法示例
    本文主要跟大家分享了shell脚本实现多进程运行的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍: 例子 for ip in 192.168.56.{1..254} do ( ping ...
    99+
    2022-06-04
    示例 脚本 进程
  • shell的多进程实现
    方法总结:    1. command & + wait 方式    2. 管道fifo       ...
    99+
    2023-06-06
  • Node中的进程间通信怎么实现
    这篇“Node中的进程间通信怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Node...
    99+
    2024-04-02
  • javascript多线程的实现方法
    本篇内容介绍了“javascript多线程的实现方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! ...
    99+
    2024-04-02
  • python多进程及通信实现异步任务的方法
    目录一、python多进程及通信基本用法1、多进程的基本实现a、Process重写run方法 b、使用Process和target方法c、直接使用Process类2、多进程...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作