iT邦幫忙

1

【我可以你也可以的Node.js】第十六篇 - Node 子行程 child_process

嗨!各位今天要介紹 child process。

這篇有牽涉到以下幾篇的內容如果還沒看得或是有不了解的可以自行斟酌看看。
【我可以你也可以的Node.js】第四篇 - 進程、行程、過程、行程、程序、處理程序、Process
【我可以你也可以的Node.js】第七篇 - 第一次用EventEmiter就用來做壞壞的事ヽ(́◕◞౪◟◕‵)ノ
【我可以你也可以的Node.js】第十一篇 - Process 的標準串流 (stdin、stdout、stderr)

發現以專案類型發文,耗時非常巨大xDD
所以會以穿插學習文章,希望可以兼具學習和興趣。 (聽起來很有夢想)


此篇學習目標 ◑ω◐ :

  • 什麼是子行程
  • 子行程種類
  • 子行程個別使用方式

什麼是子行程

維基百科是這樣說的

在電腦領域中,子行程為由另外一個行程(對應稱之為父行程)所建立的行程。子行程繼承了父行程的大部分屬性,例如檔案描述符。

簡單來說...

夠簡單吧?

這時的你...

以上純屬幹話,但是某方面來說沒有說錯xD

引用一下這位大大文章中提的

在Node.js中,提供了一個 child_process 模組,通過它可以開啟多個子程序,在多個子程序之間可以共享記憶體空間,可以通過子程序的互相通訊來實現資訊的交換。

也就是虎父無犬子,你從哪個行程創建的子行程他就繼承了該行程的大部分屬性。

所以他能幹嘛?

我個人感覺應用可以滿廣泛的我就列舉兩點我看到的~

  • 在我讀的這本書提到 OS 中有許多功能只能透過命令列存取,而 子行程 能夠做到這件事。
  • 如果沒有子行程的存在,假使是一個執行緒執行所有操作,某個操作需要消耗大量地 CPU 資源的情況下,後續操作都需要等待,那就會出大事,而子行程就可以幫助你解決這問題。

子行程種類

以下介紹四種方法建構子行程

  1. child_process.spawn
  2. child_process.exec
  3. child_process.execFile
  4. child_process.fork

我來簡單歸納一下這四種的基本介紹

方法 概述
spawn 執行命令,預設為不會新建 shell ,執行一個命令,不會回傳結果。
exec 建立 shell 並在 shell 中執行命令,可通過 callback 來獲得執行結果。
execFile 執行檔案,預設情況下不會新建 shell
fork 執行檔案,建立與父執行緒的神秘溝通管道,可以在這之中進行信息的傳送,也不會回傳結果。

子行程個別使用方式

child_process.spawn

這個是最常看到的方法。
用法就參照官方用法
child_process.spawn(command[, args][, options])
就是啟動命令傳送參數,然後建立 stdinstdoutstderr
這邊藉由 pwd 來輸出目前目錄作為示範。

const { spawn } = require('child_process');
const pwd = spawn('pwd');

pwd.stdout.on('data', (data) => {
    console.log(`stdout: ${data}`);
});

pwd.stderr.on('data', (data) => {
    console.error(`stderr: ${data}`);
});

pwd.on('close', (code) => {
    console.log(`child process exit code: ${code}`);
});

執行結果:

這邊看到成功就會藉由 stdout 做輸出
反之如果失敗就是從 stderr 做輸出
最後那個 0 代表最終結果
如果是 0 代表沒有錯誤發生
如果是 1 代表出大事啦

故意讓他從 stderr 作為輸出
const pwd = spawn('pwd');
改為
const pwd = spawn('pwd',['-g']);

執行結果:

除此之外你可能還會想說

依照書上寫的是說(完全不附帶任何責任的言論xDD)
官方文件有說到將資料導向 stdin 的範例,讓一個命令的結果直接作為另一個命令的輸入。
找到另一個官方的範例

const { spawn } = require('child_process');
const ps = spawn('ps', ['ax']);
const grep = spawn('grep', ['ssh']);

ps.stdout.on('data', (data) => {
  grep.stdin.write(data);
});

ps.stderr.on('data', (data) => {
  console.error(`ps 的 stderr: ${data}`);
});

ps.on('close', (code) => {
  if (code !== 0) {
    console.log(`ps 进程退出,退出码 ${code}`);
  }
  grep.stdin.end();
});

grep.stdout.on('data', (data) => {
  console.log(data.toString());
});

grep.stderr.on('data', (data) => {
  console.error(`grep 的 stderr: ${data}`);
});

grep.on('close', (code) => {
  if (code !== 0) {
    console.log(`grep 进程退出,退出码 ${code}`);
  }
});

我個人感覺這個的應用就是再次透過 eventEmiter 再延伸其他的動作。

補充:
child_process.spawn 還有出一個同步的版本,叫做 child_process.spawnSync 就依照個人需求去使用囉~

child_process.exec

官方文件用法
child_process.exec(command[, options][, callback])
這個也可以拿來執行命令使用方法就類似上面那個 spawn
但是最大的差異是他可以藉由 callback 拿到執行結果,
這邊就單純用 ls 列出目錄中所有的檔案當範例。

const { exec } = require('child_process');
exec('ls', (error, stdout, stderr) => {
    if (error) {
        console.error(`error: ${error}`);
        return;
    }
    console.log(`stdout: ${stdout}`);
    console.error(`stderr: ${stderr}`);
});

執行結果:

child_process.execFile

官方文件用法
child_process.execFile(file[, args][, options][, callback])
這邊要注意的是跟上面不的 child_process.exec 不同的是一個是檔案路徑,一個是命令

這邊就讓它執行我剛寫了一個檔案名稱叫 hello.js
且內容只有一行 console.log('hello world')

const { execFile } = require('child_process');
execFile('node', ['hello.js'], (error, stdout, stderr) => {
    if (error) {
        console.error(`error: ${error}`);
        return;
    }
    console.log(`stdout: ${stdout}`);
    console.error(`stderr: ${stderr}`);
});

執行結果:

另外補充一點在官方文件看到的

child_process.exec() 和 child_process.execFile() 之間區別的重要性可能因平台而異。 在 Unix 類型的操作系统(Unix、Linux、macOS)上,child_process.execFile() 可以更高效,因為預設情况下它不会產生 shell。

child_process.fork

官方文件用法
child_process.fork(modulePath[, args][, options])

這個是滿特別的,最主要是在於它會對行程建構通訊的通道。
建立兩個檔案

// fork.js
const { fork } = require('child_process')
const path = require('path')
let child = fork(path.join(__dirname, './fork1.js'))
child.on('message', (data) => {
    console.log(`父行程接收到訊息 -> ${data}`)
})
child.send('hello world')
child.on('error', (err) => {
    console.error(err)
})
//fork1.js
process.on('message', (msg, setHandle) => {
    console.log(`子行程接收到訊息 -> ${msg}`)
    process.send(msg)
})

執行結果:

此篇到此~ 有問題都可以交流討論
感謝您


參考來源

node的process以及child_process模組學習筆記
官方文件


尚未有邦友留言

立即登入留言