- 进程是由操作系统产生的
- 进程可以被视为程序的一个运行实例
- 操作系统负责给应用程序(即:进程)分配和管理资源
- 每个进程运行在其自己独立的地址空间
- 一个应用程序包含一个或多个进程
- 一个进程包含一个或多个线程
- 当操作系统去启动应用程序的时候,会给应用程序分配一个进程
- 每个进程都拥有独立的、可伸缩的内存空间
- 进程和进程之间,原则上是相互隔离的,互不影响的,但必要的时候,它们也可以进行通信
- 一个应用程序在启动时只有一个进程,但它在运行的过程中,可以开启新的进程,进程之间仍然保持相对独立
- 如果一个进程是直接由操作系统开启,则它叫做主进程
- 主进程 vs 子进程
- 主进程:当操作系统启动应用程序时,它会为该应用程序创建一个新的进程。这个进程通常被称为“主进程”或“父进程”。这是应用程序的初始和主要的运行环境。
- 子进程:主进程可以创建其他进程,这些进程通常被称为“子进程”。子进程从它们的父进程继承资源和环境设置(例如环境变量),但它们有自己独立的执行流和地址空间。
- 如果一个进程B是由进程A开启,则A是B的父进程,B是A的子进程,子进程会继承父进程的一些信息,但仍然保持相对独立。子进程发生的任何错误,不会对父进程产生任何影响,它们的内存空间是完全隔离的
- 你可以认为主进程是应用程序的“起始点”或“根”,而子进程是从主进程派生出来,执行特定的任务或功能的进程。
- 本节介绍的重点是线程而非进程
- 打开 chrome 浏览器的命令:
- win:
chrome - mac:
open "/applicaitons/google chrome.app"
- win:
- 操作系统每启动一个进程(无论是主进程,还是子进程),都会自动为它分配一个线程,称之为主线程
- 线程是进程内的执行单元,负责执行进程中的代码。
- 问:程序是在进程上运行还是在线程上运行?答:程序运行在线程上,但由于线程位于进程中,所以回答程序运行在进程上也正确。
- 主线程在运行的过程中,可以创建多个线程,这些线程称之为子线程
- 一个进程往往有多个线程
- 当操作系统命令 CPU 去执行一个进程时,实际上,是在该进程的多个线程中切换执行
- 线程和进程很相似,它们都是独立运行,最大的区别在于:线程的内存空间没有隔离,共享进程的内存空间
- 由于一个进程下的所有线程都共享这个进程的内存空间,因此线程之间的数据不用遵守任何协议,可以随意使用
- 在线程之间切换的时候,一个函数的执行可能会被打断
- 使用线程的主要目的是为了充分使用多核 CPU
- 在使用线程时,理论上最优的场景是:
- 一个 CPU 核心运行一个线程
- 线程永不阻塞
- 没有 IO
- 只存在大量运算
- 线程相对独立,几乎不使用进程提供的共享数据
- 线程并非越多越好,随着线程数量的增加,线程之间来回切换的开支也会增加
- IO 操作
- 程序耗时的主要部分集中在 IO 操作,耗时普遍占 90%↑
- 问:什么情况下线程不会阻塞?答:没有 IO 操作,只存在大量运算
- CPU 密集型 vs IO 密集型
- 线程适合用于处理 CPU 密集型操作(运算操作)
- 线程不适合用于处理 IO 密集型操作
- IO 密集型操作适合使用异步
child_process- 使用 Node.js 的
child_process模块,你可以在 Node.js 环境中创建和管理子进程 child_process可以让你从 Node.js 进程中运行其他系统命令或其他 Node.js 脚本,这样你就可以并行执行多个操作
- 使用 Node.js 的
require('child_process').exec(<命令字符串>, <回调函数>)exec是一个用于执行 shell 命令的方法- 参数 1:一个字符串形式的命令
- 参数 2:回调函数
const childProcess = require("child_process"); // 导入内置模块childProcess.exec(`xxx`, (error, stdout, stdErr) => {// 当你使用 exec 方法时,Node.js 会为你执行的命令 xxx 创建一个新的 shell 进程// 在这个新的进程中,命令会被执行,然后当命令执行完毕后,子进程将结果返回给主 Node.js 进程//// 回调函数中可以获取子进程的标准输出,这种数据交互是通过 IPC 完成的,nodejs 已经帮你完成了处理// error:开启进程过程中发生的错误(例如命令不存在或命令执行失败)// stdout: 子进程输出的正常内容// stdErr: 子进程输出的错误内容});
const childProcess = require("child_process")childProcess.exec("ls", (error, stdout, stderr) => {if (error) {console.error(`exec error:${error}`)return}console.log('stdout: ${stdout}`);})
- 任何有效的
**shell**命令,都可以丢给**childProcess.exec**执行 - mac 环境,使用
shell命令:- 查看当前目录下的文件:
ls - 打开 chrome 浏览器:
open "/applications/google chrome.app" - 打开微信:
open "/applications/wechat.app" - ……
- 查看当前目录下的文件:
const {exec} = require("child_process")exec("open '/applications/wechat.app'") // 打开微信exec("open '/applications/google chrome.app'") // 打开 chrome 浏览器
- 过去,nodejs 没有提供给用户创建线程的接口,只能使用进程的方式
- 过去,nodejs 还提供了 cluster 模块,可以通过另一种模式来创建进程
- 现在,nodejs 已经提供了线程模块,对进程的操作已经很少使用了
- Node.js 中的
worker_threads模块,允许你在 Node.js 中管理线程 - 你可以使用
**worker_threads**模块在不影响主线程的情况下执行 CPU 密集型任务,从而提高性能和并发性 - 使用 nodejs 新建子线程实例:
new Worker(<子线程文件所在位置>, <配置>) - 主线程往子线程传递数据:在新建子线程的时候,通过
workerData选项来传递数据给子线程 - 子线程接收父线程传递的数据:
require("worker_threads").workerData - 子线程往父线程选地消息:
require("worker_threads").parentPort.postMessage(data) - 父线程监听子线程的消息:
worker.on("message", (data) => { ... }) - 在父线程中终止一个子线程:
worker.terminate()

// isPrime.jsmodule.exports = function (n) {// 素数:只能被 1 和自身整除的数// 用暴力算法来实现,以便测试线程处理 CPU 密集型任务的耗时if (n < 2) {return false;}for (let i = 2; i < n; i++) {if (n % i === 0) {return false;}}return true;};
// index.jsconst {Worker} = require("worker_threads");const path = require("path");const testArr = new Array(100).fill(0).map((_, i) => i + 1)// Array.from({ length: 100 }, (_, i) => i + 1)// 新建子线程实例const worker = new Worker(path.resolve(__dirname, "worker.js"), {workerData: testArr,});worker.on("exit", () => {console.log("子线程退出了");});worker.on('message', (...args) => {console.log('received msg from worker.js => ', args)});
// worker.jsconst isPrime = require("./isPrime");const {parentPort, // 用于与父线程通信的端口workerData, // 获取线程启动时传递的数据threadId, // 获取线程的唯一编号} = require("worker_threads");const name = `线程 ${threadId}`;const newArr = [];console.log('received workerData from main thread => ', workerData)for (let i = 0; i < workerData.length; i++) {const n = workerData[i];if (isPrime(n)) {newArr.push(n);}}console.log(`${name}处理完成,并把结果给予了主线程`);parentPort.postMessage(newArr);

- demo 通过子线程充分利用 CPU 多核来处理 CPU 密集型任务
测试数据:numbers.json
// isPrime.jsmodule.exports = function (n) {// 素数:只能被 1 和自身整除的数// 用暴力算法来实现,以便测试线程处理 CPU 密集型任务的耗时if (n < 2) {return false;}for (let i = 2; i < n; i++) {if (n % i === 0) {return false;}}return true;};
// index.jsconst {Worker} = require("worker_threads")const {cpus} = require("os")const arr = require("./numbers.json")const cpuNum = cpus().lengthconst len = Math.ceil(arr.length / cpuNum) // 得到每个线程需要处理的数字数量const newArr = [] // 保存最终结果let n = cpuNum // 正在处理数据的线程数量console.time('处理耗时')for (let i = 0; i < cpuNum; i++) {const data = arr.slice(i * len, (i + 1) * len)const worker = new Worker("./worker.js", {workerData: data,})worker.on("message", (result) => {newArr.push(...result)n--if (n === 0) {console.timeEnd('处理耗时')// 所有线程都处理结束console.log(newArr) // 输出最终结果}worker.terminate()})}
// worker.jsconst isPrime = require("./isPrime");const {parentPort, // 用于与父线程通信的端口workerData, // 获取线程启动时传递的数据threadId, // 获取线程的唯一编号} = require("worker_threads");const name = `线程 ${threadId}`;const newArr = [];for (let i = 0; i < workerData.length; i++) {const n = workerData[i];if (isPrime(n)) {newArr.push(n);}}console.log(`${name} 处理完成,并把结果给予了主线程`);parentPort.postMessage(newArr);
一个 CPU 核心运行一个线程,充分利用 CPU 计算资源,耗时:709.961ms

// index.jsconst {Worker} = require("worker_threads")const arr = require("./numbers.json")console.time('处理耗时')// 新建子线程实例const worker = new Worker("./worker.js", {workerData: arr,})worker.on('message', (data) => {console.log('received msg from worker.js => ', data)worker.terminate()})worker.on("exit", () => {console.log("子线程退出了")console.timeEnd('处理耗时')})
所有数据都交给一个线程去完成,耗时:2.252s

