[node.js]向线程传递参数的方法
为了实现异步操作,我学习了线程的创建方法,但关于参数传递的方式没有太多信息,所以我把它记录下来。
在 JavaScript 中,我不确定是否合适称之为“线程”
(基本上它是以单线程方式运行的,因此其具体机制仍然不明确)
但是在尝试运行时,它似乎是以异步的方式运行的。
环境
macOS: 十点十五点五版卡塔琳娜
node.js: v14.3.0
关于线程调用类型的选择
非同步線程的創建方式有兩種:①群集②子進程調用。下面簡單介紹每種的用法。
① 是指源代碼(js文件)同時執行多個的情況
② 是指與源代碼不同的js文件同時執行的情況。
调用集群
var cluster = require('cluster');
if (cluster.isMaster) { console.log('parent'); cluster.fork(); }
else { console.log('child'); }
上述代码的执行结果如下所示。
parent
child
在这种情况下,我尝试了但无法将参数传递给每个(子)线程。
在功能上,如果有多个完全相同的事件处理程序,性能可能会提高,但是我对此还没有了解。
调用子进程
以下是一个示例步骤:
要传递参数,请将array作为fork()的第二个参数传递。
child_process.fork(要调用的JS文件名, 参数数组);
var child_process = require('child_process');
var c = child_process.fork(__dirname+'/child' , ['foo' , 'bar' ] );
被称为子进程的进程会将从父进程传递的参数存储在process.argv数组中。
以下示例将打印出所有参数元素。
process.argv.forEach( function(item) {console.log("arg:" + item);} ); //dump args
执行方式和结果如下。
$ node parent
arg:/Users/***/.nodebrew/node/v14.3.0/bin/node
arg:/Users/***/***/child
arg:foo
arg:bar
process.argv[0] : node executable file path
process.argv[1] : path of the executed js file
process.argv[2] : argument array element [0]: this time the parent process specifies “foo”.
process.argv[3] : argument array element [1]: this time the parent process specifies “bar”.
process.argv[0]:node 可执行文件路径
process.argv[1]:执行的 js 文件路径
process.argv[2]:参数数组元素 [0]:这次父进程指定了 “foo”。
process.argv[3]:参数数组元素 [1]:这次父进程指定了 “bar”。
通过 process.argv[2] 以及后续的读取方式来实际参考参数。
借助这种调用方法,可以通过父线程指示每个线程的工作内容。
家庭成员间的互通
在子进程的情况下,父子进程可以进行消息交互。两者都有on()作为接收处理器(参数是消息内容),send()作为发送处理器。
var child_process = require('child_process');
var c = child_process.fork(__dirname+'/child' , ['foo' , 'bar' ] );
c.on('message', function (msg) { console.log('[Parent]received msg = [' + msg + ']'); }); //msg handler
c.send('Hello');
process.on('message', function(msg) {
console.log('[Child]received msg = [' + msg + ']' );
if(msg == 'Hello') process.send('Hello by ' + process.argv[2]);
})
执行结果如下。
$ node parent
[Child]received msg = [Hello]
[Parent]received msg = [Hello by foo]
为了试验,我尝试调用了许多子进程。
这只是一个参考,但我试着多次调用来看看会发生什么。
var child_process = require('child_process');
const n_child = 16;
var _childlen =[];
for( var i = 0 ; i < n_child ; ++i) {
_childlen[i] = child_process.fork(__dirname+'/child' , [ i ] );
_childlen[i].on('message', function (msg) { console.log('[Parent]received msg = ['+ msg + ']' ); });
}
_childlen.forEach( function(a) { a.send('Hello'); });
process.on('message', function(msg) {
console.log('[Child]received msg = [' + msg + ']' );
if(msg == 'Hello') process.send('Hello by ' + process.argv[2]);
})
实际执行的结果如下:
可以看出父进程的send()操作和子进程的回复操作是异步(同时)执行的。
收到来自child的消息时,父进程的dump([Parent] received)是无序的,这很有意思。
这是由内部处理来确定事件处理程序的顺序(仅是猜测)。
在父进程的事件处理等待队列中,它们被认为是无序的,
这意味着child的发送处理(共16个)是异步进行的。
简单地考虑,如果每个子进程都作为单独的线程运行,效率会相当高。
如果遇到性能问题,可以考虑使用这种方式来进行并行处理等手段。
$node parent
Child]received msg = [Hello]
[Parent]received msg = [Hello by 0]
[Child]received msg = [Hello]
[Child]received msg = [Hello]
[Child]received msg = [Hello]
[Child]received msg = [Hello]
[Parent]received msg = [Hello by 3]
[Parent]received msg = [Hello by 5]
[Parent]received msg = [Hello by 6]
[Parent]received msg = [Hello by 1]
[Child]received msg = [Hello]
[Parent]received msg = [Hello by 4]
[Child]received msg = [Hello]
[Parent]received msg = [Hello by 2]
[Child]received msg = [Hello]
[Parent]received msg = [Hello by 7]
[Child]received msg = [Hello]
[Parent]received msg = [Hello by 11]
[Child]received msg = [Hello]
[Parent]received msg = [Hello by 8]
[Child]received msg = [Hello]
[Child]received msg = [Hello]
[Parent]received msg = [Hello by 10]
[Parent]received msg = [Hello by 12]
[Child]received msg = [Hello]
[Parent]received msg = [Hello by 9]
[Child]received msg = [Hello]
[Parent]received msg = [Hello by 14]
[Child]received msg = [Hello]
[Parent]received msg = [Hello by 13]
[Child]received msg = [Hello]
[Parent]received msg = [Hello by 15]
查看操作系统的进程,确实发现每个子进程(线程)都生成了一个进程(线程)。
6049 s000 S+ 0:00.01 grep node
6023 s002 S+ 0:00.20 node parent
6024 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 0
6025 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 1
6026 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 2
6027 s002 S+ 0:00.11 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 3
6028 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 4
6029 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 5
6030 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 6
6031 s002 S+ 0:00.11 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 7
6032 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 8
6033 s002 S+ 0:00.09 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 9
6034 s002 S+ 0:00.09 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 10
6035 s002 S+ 0:00.09 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 11
6036 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 12
6037 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 13
6038 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 14
6039 s002 S+ 0:00.09 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 15
我试着叫了更多人。
我尝试将上述代码中的子进程数量增加。
:
const n_child = 4096;
:
The outcome is as follows.
$node parent
child : 0
child : 1
:
:
child :385
child :405
(libuv) kqueue(): Too many open files in system
/Users/***/.nodebrew/node/v14.3.0/bin/node[7040]: ../src/tracing/agent.cc:55:node::tracing::Agent::Agent(): Assertion `(uv_loop_init(&tracing_loop_)) == (0)' failed.
:
:
Error: ENFILE: file table overflow, uv_pipe_open
:
SystemErr
当子进程数量超过400个时,出现了错误提示。
我本以为是操作系统的线程数达到了极限,但实际上是由于node.js系统的libuv(负责处理IO相关操作的部分)处理管道数量达到了极限。
我认为这是在父子进程之间通信所使用的。
在执行过程中,我所使用的浏览器也遇到了读取错误,所以我认为这对操作系统也造成了负担。
由于进程过多(每个进程都需要进行一定的初始化等处理),导致网络处理跟不上。但MacOS本身并未出现崩溃等问题。
虽然过多地进行调用是不好的,但也许通过延迟一段时间再启动子进程,问题就会消失。(以上都是猜测)