child_process - 子进程

进程

- 定义

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

了解 CPU

- 特点

  1. 一个单核 CPU,在一个时刻,只能做一件事情
  2. 那么如何让用户同时看电影、听音乐、写代码的呢?
  3. 答案是在不同的进程中快速切换(有多快呢?主要看 CPU 的主频,每秒几百万次也有可能)

多程序并发执行

  1. 指多个程序在宏观上并行,微观上串行
  2. 每个进程会出现【执行 - 暂停 - 执行】的规律
  3. 多个进程之间会出现抢资源(如打印机)的现象

阻塞

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

进程的三个状态

线程 Thread

- 分阶段

在 linux 2.4 之前,操作系统只有进程没有线程

  1. 在面向进程设计的系统中,进程是程序的基本执行实体
  2. 在面向线程设计的系统中,进程本身不是基本运行单位,还是线程的容器

- 引入原因

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

Node.js 的进程控制

Node.js 的线程控制

概念

  1. CPU 调度和执行的最小单位
  2. 一个进程中至少有一个线程,可以有多个线程
  3. 一个进程中的线程共享该进程的所有资源
  4. 进程的第一个线程叫做初始化线程
  5. 线程的调度可以由操作系统负责,也可以用户自己负责

举例

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

分析

  • 子进程 VS 线程
  • 都能满足重开一个子任务,优先使用线程,除非你需要单独的资源分配

Node.js 中的 child_process (用于新建子进程)

- 使用目的

  1. 子进程的运行结果储存在系统缓存之中(最大200kb)
  2. 等到子进程运行结束以后,主进程再用回调函数读取子进程的运行结果
简单的 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)
})

有漏洞,可以被注入,可能执行意外的代码(如上 userInput)
所以推荐使用 execFile(因为参数通过另外传参)
const child_process = require('child_process')

const { execFile } = child_process

const options = { pwd: 'C:\\', env: { NODE_ENV: 'development' } }
// options 常用的选项
// cwd - Current working directory
// env 环境变量
// shell 用什么 shell
// maxBuffer 最大缓存,默认 1024 * 1024 字节
const userInput = '-al && pwd' // '-al && rm -fm *'
// 这里会报错
execFile('ls', ['-al', userInput], options, (error, stdout, stderr) => {
  console.log(error)
  console.log(stdout)
  console.log(stderr)
})

相比上面更推荐使用 spawn
  1. 用法和 execFile 方法类似
  2. 没有回调函数,只能通过流事件获取结果
  3. 没有最大 200 kb 的限制(因为是流)
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

  1. 创建一个子进程,执行 Node 脚本(正因为如此,我们大多数时候都是执行 Node 脚本而不是 Bash,所以一般都用 fork)
  2. fork('./child.js) 相当于 spawn('node', ['./child.js'])
特点
  1. 会多出一个 message 事件,用于父子通信
  2. 会多出一个 send 方法

father.js

const child_process = require('child_process')

const child = child_process.fork('./child.js')

child.on('message', message => {
  console.log(message)
})

// 或父给子发信息
// child.send({ hello: 'world' })

child.js

setTimeout(() => {
  process.send('这是 child 传来的消息')
}, 3000)

process.on('message', message => {
  console.log('这里接受父传子的消息')
  console.log(message)
})

一些历史

为什么不用线程,因为太新了,而且效率不够高(文档 中文中写明)

  • child_process.exec
  • v0.1.90 加入 Node.js
  • new Worker
  • v10.5.0 加入 Node.js (去年才加入)
  • v11.7.0 之前需要 --experimental-worker 开启

简单介绍 worker_threads

  • api 列表
  1. isMainThread
  2. new Worker(filename)
  3. parentPort
  4. postMessage
  • 事件列表
  1. message
  2. exit

你可能感兴趣的:(child_process - 子进程)