Node全解10_01child_process

child_process 子进程

进程Process

场景

  • notepad.exe是一个程序,不是进程
  • 双击 notepad.exe时,操作系统开启了一个进程

定义

  • 进程是程序的执行实例
  • 程序在CPU上执行时的活动叫做进程
  • 实际上并没有明确的定义,只有一些规则

特点

  • 一个进程可以创建另一个进程(父进程和子进程)
  • 通过任务管理器可以查看到进程

了解 CPU

特点

  • 一个单核CPU,在一个时刻,只能做一件事
  • 那么如何让用户同时看电影,听歌,写代码呢?
  • 答案是在不同进程中快速切换

多进程并发执行

  • 指多个程序在宏观上并行,微观上串行
  • 每个进程会出现 “执行-暂停-执行”的规律
  • 多个进程之前会出现抢资源(如打印机)的现象

阻塞

等待执行的进程中

  • 都是非运行态
  • 一些(A)在等待 CPU资源
  • 另一些(B)在等待 I/O完成 (如文件读取)
  • 如果这个时候把 CPU分配给 B 进程, B还是在等 I/O
  • 我们把这个 B 叫做阻塞进程
  • 因此,分派程序只会把 CPU 分配给非阻塞进程

进程三个状态

1
2
3
就绪     ==》    运行 
| |
《== 阻塞 《==

线程 Thread 引入

  • 以前: 面向进程设计的系统中,进程是程序的基本执行实体
  • 后来: 面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器

引入原因

  • 进程是执行的基本实体,也是资源分配的基本实体
  • 导致进程的创建、切换、销毁太消耗CPU时间了
  • 于是引入了线程,线程作为执行的基本实体
  • 而进程作为资源分配的基本实例

线程 Thread

  • 最新的操作系统里:线程才是CPU调度和执行的最小单元
  • 一个进程中至少有一个线程,可以有多个线程
  • 一个进程中的线程共享该进程的所有资源
  • 进程的第一个线程叫做初始化线程
  • 线程的调度可以由操作系统负责,也可以由用户自己负责

举例

  • 浏览器进程有渲染引擎、V8引擎、存储模块、网络模块、用户界面模块等
  • 每个模块都可以放在一个线程里

分析

  • 子进程 VS 线程
    • 优先使用 线程

child_process

Node.js操作子进程

使用目的

  • 子进程的运行结果存储在系统缓存之中(最大200KB)
  • 等到子进程运行结束之后,主进程再用回调函数读取子进程的运行结果
1
2
3
4
5
6
7
8
9
const child_process = require('child_process');

const { exec } = child_process;

exec('ls', (error, stdout, stderr) => {
console.log(error);
console.log(stdout);
console.log(stderr);
})

exec API

exec(cmd,options,fn)

  • execute 的缩写,用于执行 bash命令
  • 同步版本:execSync

  • 返回一个流
1
2
3
4
5
const streams = exec('ls');
// 不用回调 用事件
streams.stdout.on('data', (chunk) => {
console.log(chunk)
})

Promise

  • 可以使其 Promise 化(util.promisify)
1
2
3
4
5
6
7
8
9
const util = require('util');
const child_process = require('child_process');
const { exec } = child_process;

const exec2 = util.promisify(exec);

exec2('ls').then((data) => {
console.log(data.stdout);
})

有漏洞

  • 如果cmd 被注入了,可能执行意外的代码
  • 推荐使用 execFile
1
2
3
4
5
6
// 类似 sql 那种 “;” 结束上一个命令 然后注入它的命令
exec('ls; pwd', (error, stdout, stderr) => {
console.log(error);
console.log(stdout);
console.log(stderr);
})

推荐使用 execFile 而不是 exec

execFile API

  • 执行特定的程序
  • 命令行参数用数组的形式传入,无法注入
  • 同步版本: execFileSync
1
2
3
4
5
6
7
8
9
10
const child_process = require('child_process');

const { execFile } = child_process;

const user_input = '. && pwd';

execFile('ls', ['-la', user_input], (error, stdout, stderr) => {
console.log(error);
console.log(stdout);
})
  • 同样也支持流

options 参数

  • cwd
  • env
  • shell
  • maxBuffer 最大缓存 默认 1024*1024 字节
1
2
3
4
5
6
7
8
9
10
11
12
13
const child_process = require('child_process');

const { execFile } = child_process;

execFile('ls', ['-la'], {
cwd: '/', // 当前命令执行的工作目录
env: { NODE_ENV: 'development' },// 环境变量
maxBuffer: 1024 * 1024,// 设置最大缓存
// shell: 设置用什么 shell
}, (error, stdout) => {
console.log(error)
console.log(stdout)
})

spawn

  • 用法跟 execFile 几乎一样
  • 没有回调函数,只能通过流事件获取结果
  • 没有最大 200KB 的限制(因为是流)

经验

  • 能用 spawn 的时候就不要用 execFile
1
2
3
4
5
6
7
8
9
10
const child_process = require('child_process');
const { spawn } = child_process;

const streams = spawn('ls', ['-la'], {
cwd: '/'
});

streams.stdout.on('data', (chunk) => {
console.log(chunk.toString())
})

fork

  • 创建一个子进程,执行Node脚本
  • fork(‘./child.js’) 相当于 spawn('node',['./child.js'])

特点

  • 会多出一个 message 事件,用于父子通信
  • 会多出一个 send 方法

父.js

1
2
3
4
5
6
7
const child_process = require('child_process');

var n = child_process.fork('./child.js');
n.on('message', function (m) {
console.log('父进程得到了值:', m);
});
n.send({ hello: 'world' });

child.js

1
2
3
4
5
6
7
setTimeout(() => {
process.send({ foo: 'bar' });
}, 2000);

process.on('message', function (m) {
console.log('子进程得到消息:', m);
});

操作线程

一些历史

  • child_process.exec
    • v0.1.90 加入 Node.js
  • new Worker
    • v10.5.0 加入 Node.js
    • v11.7.0 之前需要 –experimental-worker开启
  • 这个线程API太新了
    • 所以我们应该不会经常用到
  • 效率
    • 目前效率并不算很高, 文档中文 自己都写了

worker_threads API

  • isMainThread
  • new Worker(filename)
  • parentPort 线程间通信

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const { Worker, isMainThread, parentPort } = require('worker_threads');

    if (isMainThread) {
    const worker = new Worker(__filename);
    worker.once('message', (message) => {
    console.log(message); // Prints 'Hello, world!'.
    });
    worker.postMessage('Hello, world!');
    } else {
    // When a message from the parent thread is received, send it back:
    parentPort.once('message', (message) => {
    parentPort.postMessage(message);
    });
    }
  • postMessage

事件列表

  • message
  • exit

总结

  • 如果你对进程,线程感兴趣推荐学习任意一本关于操作系统的教科书
  • 代码仓库