進(jìn)程
- 定義
- 進(jìn)程是程序的執(zhí)行示例
- 程序在 CPU 上執(zhí)行的活動(dòng)叫做進(jìn)程
- 實(shí)際上并沒(méi)有明確的定義,只有一些規(guī)則
了解 CPU
- 特點(diǎn)
- 一個(gè)單核 CPU,在一個(gè)時(shí)刻,只能做一件事情
- 那么如何讓用戶同時(shí)看電影、聽(tīng)音樂(lè)、寫(xiě)代碼的呢?
- 答案是在不同的進(jìn)程中快速切換(有多快呢?主要看 CPU 的主頻,每秒幾百萬(wàn)次也有可能)
多程序并發(fā)執(zhí)行
- 指多個(gè)程序在宏觀上并行,微觀上串行
- 每個(gè)進(jìn)程會(huì)出現(xiàn)【執(zhí)行 - 暫停 - 執(zhí)行】的規(guī)律
- 多個(gè)進(jìn)程之間會(huì)出現(xiàn)搶資源(如打印機(jī))的現(xiàn)象
阻塞
- 等待進(jìn)程中的進(jìn)程中
- 都是非運(yùn)行的狀態(tài)
- 一些(A)在等待 CPU 資源
- 另一些(B)在等待 I/O 完成(如文件讀?。?/li>
- 如果這個(gè)時(shí)候把 CPU 分配給 B 進(jìn)程,B 還是在等待 I/O
- 我們把這個(gè) B 叫做阻塞進(jìn)程
- 因此,分派程序只會(huì)把CPU 分配給非阻塞進(jìn)程
進(jìn)程的三個(gè)狀態(tài)

線程 Thread
- 分階段
在 linux 2.4 之前,操作系統(tǒng)只有進(jìn)程沒(méi)有線程
- 在面向進(jìn)程設(shè)計(jì)的系統(tǒng)中,進(jìn)程是程序的基本執(zhí)行實(shí)體
- 在面向線程設(shè)計(jì)的系統(tǒng)中,進(jìn)程本身不是基本運(yùn)行單位,還是線程的容器
- 引入原因
- 進(jìn)程是執(zhí)行的基本實(shí)體,也是資源分配的基本實(shí)體
- 導(dǎo)致進(jìn)程的創(chuàng)建。切換、銷毀太消耗CPU 時(shí)間了
- 于是引入線程,線程作為執(zhí)行的基本實(shí)體
- 而進(jìn)程只作為資源分配的基本實(shí)體
Node.js 的進(jìn)程控制
Node.js 的線程控制
概念
- CPU 調(diào)度和執(zhí)行的最小單位
- 一個(gè)進(jìn)程中至少有一個(gè)線程,可以有多個(gè)線程
- 一個(gè)進(jìn)程中的線程共享該進(jìn)程的所有資源
- 進(jìn)程的第一個(gè)線程叫做初始化線程
- 線程的調(diào)度可以由操作系統(tǒng)負(fù)責(zé),也可以用戶自己負(fù)責(zé)
舉例
- 瀏覽器進(jìn)程里面有渲染引擎、V8引擎、儲(chǔ)存模塊、網(wǎng)絡(luò)模塊。用戶界面模塊等
- 每個(gè)模塊都可以放在一個(gè)線程里
分析
- 子進(jìn)程 VS 線程
- 都能滿足重開(kāi)一個(gè)子任務(wù),優(yōu)先使用線程,除非你需要單獨(dú)的資源分配
Node.js 中的 child_process (用于新建子進(jìn)程)
- 使用目的
- 子進(jìn)程的運(yùn)行結(jié)果儲(chǔ)存在系統(tǒng)緩存之中(最大200kb)
- 等到子進(jìn)程運(yùn)行結(jié)束以后,主進(jìn)程再用回調(diào)函數(shù)讀取子進(jìn)程的運(yùn)行結(jié)果
簡(jiǎn)單的 exec 栗子:
const child_process = require('child_process')
const { exec } = child_process
const userInput = '-al && pwd' // '-al && rm -fm *'
exec(`ls ${userInput}`, (error, stdout, stderr) => {
console.log(error)
console.log(stdout)
console.log(stderr)
})
有漏洞,可以被注入,可能執(zhí)行意外的代碼(如上 userInput)
所以推薦使用 execFile(因?yàn)閰?shù)通過(guò)另外傳參)
const child_process = require('child_process')
const { execFile } = child_process
const options = { pwd: 'C:\\', env: { NODE_ENV: 'development' } }
// options 常用的選項(xiàng)
// cwd - Current working directory
// env 環(huán)境變量
// shell 用什么 shell
// maxBuffer 最大緩存,默認(rèn) 1024 * 1024 字節(jié)
const userInput = '-al && pwd' // '-al && rm -fm *'
// 這里會(huì)報(bào)錯(cuò)
execFile('ls', ['-al', userInput], options, (error, stdout, stderr) => {
console.log(error)
console.log(stdout)
console.log(stderr)
})
相比上面更推薦使用 spawn
- 用法和 execFile 方法類似
- 沒(méi)有回調(diào)函數(shù),只能通過(guò)流事件獲取結(jié)果
- 沒(méi)有最大 200 kb 的限制(因?yàn)槭橇鳎?/li>
const child_process = require('child_process')
const { spawn } = child_process
const userInput = '.'
const options = { pwd: 'C:\\', env: { NODE_ENV: 'development' } }
const streams = spawn('ls', ['-al', userInput], options)
streams.stdout.on('data', chunk => {
console.log(chunk.toString())
})
但是我們最常用的還是 fork
- 創(chuàng)建一個(gè)子進(jìn)程,執(zhí)行 Node 腳本(正因?yàn)槿绱?,我們大多?shù)時(shí)候都是執(zhí)行 Node 腳本而不是 Bash,所以一般都用 fork)
- fork('./child.js) 相當(dāng)于 spawn('node', ['./child.js'])
特點(diǎn)
- 會(huì)多出一個(gè) message 事件,用于父子通信
- 會(huì)多出一個(gè) send 方法
father.js
const child_process = require('child_process')
const child = child_process.fork('./child.js')
child.on('message', message => {
console.log(message)
})
// 或父給子發(fā)信息
// child.send({ hello: 'world' })
child.js
setTimeout(() => {
process.send('這是 child 傳來(lái)的消息')
}, 3000)
process.on('message', message => {
console.log('這里接受父?jìng)髯拥南?)
console.log(message)
})
一些歷史
為什么不用線程,因?yàn)樘铝?,而且效率不夠高?a target="_blank">文檔 中文中寫(xiě)明)
- child_process.exec
- v0.1.90 加入 Node.js
- new Worker
- v10.5.0 加入 Node.js (去年才加入)
- v11.7.0 之前需要 --experimental-worker 開(kāi)啟
簡(jiǎn)單介紹 worker_threads
- api 列表
- isMainThread
- new Worker(filename)
- parentPort
- postMessage
- 事件列表
- message
- exit