众所周知Node基于V8,而在V8中JavaScript是单线程运行的,这里的单线程不是指Node启动的时候就只有一个线程,而是说运行JavaScript代码是在单线程上,Node还有其他线程,比如进行异步IO操作的IO线程。这种单线程模型带来的好处就是系统调度过程中不会频繁进行上下文切换,提升了单核CPU的利用率。
但是这种做法有个缺陷,就是我们无法利用服务器CPU多核的性能,一个Node进程只能利用一个CPU。而且单线程模式下一旦代码崩溃就是整个程序崩溃。通常解决方案就是使用Node的cluster模块,通过master-worker
模式启用多个进程实例。下面我们详细讲述下,Node如何使用多进程模型利用多核CPU,以及自带的cluster模块具体的工作原理。
如何创建子进程
node提供了child_process
模块用来进行子进程的创建,该模块一共有四个方法用来创建子进程。
const { spawn, exec, execFile, fork } = require('child_process') spawn(command[, args][, options]) exec(command[, options][, callback]) execFile(file[, args][, options][, callback]) fork(modulePath[, args][, options])
spawn
首先认识一下spawn方法,下面是Node文档的官方实例。
const { spawn } = require('child_process'); const child = spawn('ls', ['-lh', '/home']); child.on('close', (code) => { console.log(`子进程退出码:$[code]`); }); const { stdin, stdout, stderr } = child stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); stderr.on('data', (data) => { console.log(`stderr: ${data}`); });
通过spawn创建的子进程,继承自EventEmitter,所以可以在上面进行事件(discount
,error
,close
,message
)的监听。同时子进程具有三个输入输出流:stdin、stdout、stderr,通过这三个流,可以实时获取子进程的输入输出和错误信息。
这个方法的最终实现基于libuv,这里不再展开讨论,感兴趣可以查看源码。
// 调用libuv的api,初始化一个进程 int err = uv_spawn(env->event_loop(), &wrap->process_, &options);
exec/execFile
之所以把这两个放到一起,是因为exec最后调用的就是execFile方法。唯一的区别是,exec中调用的normalizeExecArgs
方法会将opts的shell属性默认设置为true。
exports.exec = function exec(/* command , options, callback */) { const opts = normalizeExecArgs.apply(null, arguments); return exports.execFile(opts.file, opts.options, opts.callback); }; function normalizeExecArgs(command, options, callback) { options = { ...options }; options.shell = typeof options.shell === 'string' ? options.shell : true; return { options }; }
内容版权声明:除非注明,否则皆为本站原创文章。