- 什么是进程
- 场景
- notepad.exe是一个程序,不是进程
- 双击notepad.exe时,操作系统会开启一个进程
- 定义
- 进程是程序的执行实例
- 程序在CPU上执行时的活动叫做进程
- 实际上并没有明确的定义,只有一些规则
- 特点
- 一个进程可以创建另一个进程(父进程与子进程)
- 通过任务管理器可以看到进程
- 了解CPU
- 特点
- 一个单核CPU,在一个时刻,只能做一件事情
- 那么如何让用户同时看电影、听声音、写代码呢?
- 答案是在不同进程中快速切换
- 多程序并发执行
- 指多个程序在宏观上并行,微观上串行
- 每个进程会出现「执行-暂停-执行」的规律
- 多个进程之间会出现抢资源(如打印机被占用)的现象
- 进程的两个状态


- 什么事进程阻塞
- 等待执行的进程中都是非运行态
- 某一些(A)在等待CPU资源另一些(B)在等待I/O完成(如文件读取)
- 如果这个时候把CPU分配给B进程,B还是在等I/O
- 我们把这个B叫做阻塞进程,因此,分派程序只会把CPU分配给非阻塞进程
- 了解阻塞的概念之后,进程的状态就会多一个阻塞态,非运行变成就绪和阻塞两种

- 线程Thread的引入
- 线程的引入,分为以下两个阶段
- 在面向进程设计的系统中,进程是程序的基本执行实体
- 在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器
- 引入原因
- 进程是执行的基本实体,也是资源分配的基本实体
- 导致进程的创建、切换、销毁太消耗CPU时间了,于是引入线程,线程作为执行的基本实体
- 而进程只作为资源分配的基本实体,此处可以以设计师和工程师分开招聘举例
- 线程Thread的概念
- 基础概念
- CPU调度和执行的最小单元
- 一个进程中至少有一个线程,可以有多个线程
- 一个进程中的线程共享该进程的所有资源
- 进程的第一个线程叫做初始化线程
- 线程的调度可以由操作系统负责,也可以用户自己负责
- 举例
- 浏览器进程里面有渲染引擎、V8引擎、存储模块、网络模块、用户界面模块等
- 每个模块都可以放在一个线程里
- node-js的child_process之exec的使用
- 使用目的
- 子进程的运行结果储存在系统缓存之中(最大200Kb)
- 等到子进程运行结束以后,主进程再用回调函数读取子进程的运行结果
- 这段代码是使用Node.js中的child_process模块来执行操作系统的命令。
- 具体来说,它执行了一个ls命令,该命令用于列出指定目录中的文件和子目录。
const child_process = require('child_process')
const {exec} = child_process
exec('ls ../', (error, stdout, stderr)=>{
console.log(error)
console.log(stdout)
console.log(stderr)
})
const { exec } = require('child_process');
const child = exec('ls ../');
child.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
child.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child.on('close', (code) => {
console.log(`子进程退出,退出码 ${code}`);
});
- 我们也可以用Node.js的util.promisify来将exec方法转换为返回Promise的形式
const child_process = require('child_process')
const util = require("util")
const {exec} = child_process
const exec2 = util.promisify(exec)
exec2('ls ../').then(data=>{
console.log(data.stdout)
})
- exec的风险
- exec有风险可能会被注入,执行意外的代码。下面代码中如果用户输入包含恶意代码,可能导致不安全的命令执行。
const child_process = require('child_process')
const util = require("util")
const {exec} = child_process
const exec2 = util.promisify(exec)
const userInput = '. && pwd'
exec2(`ls ${userInput}`).then(data=>{
console.log(data.stdout)
})
- 避免风险exec的风险使用execFile
const child_process = require('child_process')
const util = require("util")
const {execFile} = child_process
const userInput = ".";
execFile("ls", ["-la", userInput], (error, stdout) => {
console.log(error)
console.log(stdout)
})
const userInput = ".";
const streams = execFile("ls", ["-la", userInput])
streams.stdout.on('data', (chunk)=> {
console.log(chunk)
})
- execFile的options参数
- cwd-Current working directory
- env-环境变量
- shell-用什么shell
- maxBuffer-最大缓存,默认1024*1024字节
execFile("ls", ["-la", userInput], {
cwd: '/',
env: {
NODE_ENV: 'development'
},
shell: 'bash',
maxBuffer: 1024 * 1024
}, (error, stdout) => {
console.log(error)
console.log(stdout)
})
- 第三个Api,spawn
- 中文意思是产卵
- 用法与execFile方法类似
- 没有回调函数,只能通过流事件获取结果
- 没有最大200Kb的限制(因为是流)
- 经验
- 能用spawn的时候就不要用execFile,速度快也没有缓存大小限制
const userInput = ".";
const streams = spawn("ls", ["-la", userInput], {
cwd: '/',
env: {
NODE_ENV: 'development'
},
})
streams.stdout.on('data', (chunk)=> {
console.log(chunk.toString())
})
- 第四个api,fork
- 作用
- 创建一个子进程,执行Node脚本
- fork('./child.js')相当于spawn('node',['./child.js'])
- 特点
- 会多出一个message事件,用于父子通信
- 会多出一个send方法
- 例子
const child_process = require('child_process')
let n = child_process.fork('./child.js');
n.on('message', function (m) {
console.log('父进程得到值:', m);
});
setTimeout(()=>{
process.send({foo: 'bar'});
},2000)

- 总结:
- fork像是spawn的语法糖,但是经常只用fork
- 子线程的使用
- worker threads
- API列表
- isMainThread
- new Worker(filename)
- parentPort
- postMessage
- 事件列表
- 例子
const {Worker, isMainThread, parentPort} = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
worker.once('message', (message) => {
console.log('father', message);
});
worker.postMessage('Hello,world!');
} else {
parentPort.once('message', (message) => {
console.log('son start')
parentPort.postMessage(message);
console.log('son done')
});
}
