深入浅出:理解进程、线程与Node.js子进程的完整指南

  1. 什么是进程
  • 场景
    • notepad.exe是一个程序,不是进程
    • 双击notepad.exe时,操作系统会开启一个进程
  • 定义
    • 进程是程序的执行实例
    • 程序在CPU上执行时的活动叫做进程
    • 实际上并没有明确的定义,只有一些规则
  • 特点
    • 一个进程可以创建另一个进程(父进程与子进程)
    • 通过任务管理器可以看到进程
  1. 了解CPU
  • 特点
    • 一个单核CPU,在一个时刻,只能做一件事情
    • 那么如何让用户同时看电影、听声音、写代码呢?
    • 答案是在不同进程中快速切换
  • 多程序并发执行
    • 指多个程序在宏观上并行,微观上串行
    • 每个进程会出现「执行-暂停-执行」的规律
    • 多个进程之间会出现抢资源(如打印机被占用)的现象
  1. 进程的两个状态
  • 非运行和运行状态的切换
深入浅出:理解进程、线程与Node.js子进程的完整指南
  • 进程队列分派cpu的过程
深入浅出:理解进程、线程与Node.js子进程的完整指南
  1. 什么事进程阻塞
  • 等待执行的进程中都是非运行态
  • 某一些(A)在等待CPU资源另一些(B)在等待I/O完成(如文件读取)
  • 如果这个时候把CPU分配给B进程,B还是在等I/O
  • 我们把这个B叫做阻塞进程,因此,分派程序只会把CPU分配给非阻塞进程
  • 了解阻塞的概念之后,进程的状态就会多一个阻塞态,非运行变成就绪和阻塞两种
深入浅出:理解进程、线程与Node.js子进程的完整指南
  1. 线程Thread的引入
  • 线程的引入,分为以下两个阶段
    • 在面向进程设计的系统中,进程是程序的基本执行实体
    • 在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器
  • 引入原因
    • 进程是执行的基本实体,也是资源分配的基本实体
    • 导致进程的创建、切换、销毁太消耗CPU时间了,于是引入线程,线程作为执行的基本实体
    • 而进程只作为资源分配的基本实体,此处可以以设计师和工程师分开招聘举例
  1. 线程Thread的概念
  • 基础概念
    • CPU调度和执行的最小单元
    • 一个进程中至少有一个线程,可以有多个线程
    • 一个进程中的线程共享该进程的所有资源
    • 进程的第一个线程叫做初始化线程
    • 线程的调度可以由操作系统负责,也可以用户自己负责
  • 举例
    • 浏览器进程里面有渲染引擎、V8引擎、存储模块、网络模块、用户界面模块等
    • 每个模块都可以放在一个线程里
  1. 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');

// 使用 exec 方法创建子进程
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)
})
  1. 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)
}) 
  1. 避免风险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)
})
  • execFile支持流,可以改写成流的形式
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',
  maxBuffer1024 * 1024 
}, (error, stdout) => {
  console.log(error)
  console.log(stdout)
})
  1. 第三个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())
})
  1. 第四个api,fork
  • 作用
    • 创建一个子进程,执行Node脚本
    • fork(‘./child.js’)相当于spawn(‘node’,[‘./child.js’])
  • 特点
    • 会多出一个message事件,用于父子通信
    • 会多出一个send方法
  • 例子
    • 新建n.js和child.js
    • n.js
const child_process = require('child_process')
let n = child_process.fork('./child.js');
n.on('message'function (m{
  console.log('父进程得到值:', m);
});
  • child.js
setTimeout(()=>{
  process.send({foo'bar'});
},2000)
  • 可以得到如下结果
深入浅出:理解进程、线程与Node.js子进程的完整指南
  • 总结:
    • fork像是spawn的语法糖,但是经常只用fork
  1. 子线程的使用
  • worker threads
  • API列表
    • isMainThread
    • new Worker(filename)
    • parentPort
    • postMessage
  • 事件列表
    • message
    • exit
  • 例子
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')
  });
}
深入浅出:理解进程、线程与Node.js子进程的完整指南


原文始发于微信公众号(前端之乐):深入浅出:理解进程、线程与Node.js子进程的完整指南

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/274556.html

(0)
OriesZhu的头像OriesZhubm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!