眾所周知Node基于V8,而在V8中JavaScript是單線程運(yùn)行的,這里的單線程不是指Node啟動(dòng)的時(shí)候就只有一個(gè)線程,而是說運(yùn)行JavaScript代碼是在單線程上,Node還有其他線程,比如進(jìn)行異步IO操作的IO線程。這種單線程模型帶來的好處就是系統(tǒng)調(diào)度過程中不會(huì)頻繁進(jìn)行上下文切換,提升了單核CPU的利用率。
但是這種做法有個(gè)缺陷,就是我們無法利用服務(wù)器CPU多核的性能,一個(gè)Node進(jìn)程只能利用一個(gè)CPU。而且單線程模式下一旦代碼崩潰就是整個(gè)程序崩潰。通常解決方案就是使用Node的cluster模塊,通過master-worker模式啟用多個(gè)進(jìn)程實(shí)例。下面我們?cè)敿?xì)講述下,Node如何使用多進(jìn)程模型利用多核CPU,以及自帶的cluster模塊具體的工作原理。
node提供了child_process模塊用來進(jìn)行子進(jìn)程的創(chuàng)建,該模塊一共有四個(gè)方法用來創(chuàng)建子進(jìn)程。
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
首先認(rèn)識(shí)一下spawn方法,下面是Node文檔的官方實(shí)例。
const { spawn } = require('child_process');const child = spawn('ls', ['-lh', '/home']);child.on('close', (code) => { console.log(`子進(jìn)程退出碼:$[code]`);});const { stdin, stdout, stderr } = childstdout.on('data', (data) => { console.log(`stdout: ${data}`);});stderr.on('data', (data) => { console.log(`stderr: ${data}`);});通過spawn創(chuàng)建的子進(jìn)程,繼承自EventEmitter,所以可以在上面進(jìn)行事件(discount,error,close,message)的監(jiān)聽。同時(shí)子進(jìn)程具有三個(gè)輸入輸出流:stdin、stdout、stderr,通過這三個(gè)流,可以實(shí)時(shí)獲取子進(jìn)程的輸入輸出和錯(cuò)誤信息。
這個(gè)方法的最終實(shí)現(xiàn)基于libuv,這里不再展開討論,感興趣可以查看源碼。
// 調(diào)用libuv的api,初始化一個(gè)進(jìn)程int err = uv_spawn(env->event_loop(), &wrap->process_, &options);
exec/execFile
之所以把這兩個(gè)放到一起,是因?yàn)閑xec最后調(diào)用的就是execFile方法。唯一的區(qū)別是,exec中調(diào)用的normalizeExecArgs方法會(huì)將opts的shell屬性默認(rèn)設(shè)置為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 };}在execFile中,最終調(diào)用的是spawn方法。
新聞熱點(diǎn)
疑難解答
圖片精選