Node.js 工作线程与子进程:应该使用哪一个

2023-12-17

Node.js 工作线程与子进程:应该使用哪一个

并行处理在计算密集型应用程序中起着至关重要的作用。例如,考虑一个确定给定数字是否为素数的应用程序。如果我们熟悉素数,我们就会知道必须从 1 遍历到该数的平方根才能确定它是否是素数,而这通常非常耗时且计算量极大。

因此,如果我们在 Node.js 上构建此类计算量大的应用程序,我们可能会阻塞正在运行的线程很长时间。由于 Node.js 的单线程特性,不涉及 I/O 的计算密集型操作将导致应用程序停止,直到该任务完成。

因此,在构建需要执行此类任务的软件时,我们不会使用 Node.js。但是,Node.js 引入了工作线程 和子进程的概念 来帮助在 Node.js 应用程序中进行并行处理,以便我们可以并行执行特定进程。在本文中,我们将了解这两个概念并讨论何时使用它们。

Node.js 中的工作线程是什么

Node.js 能够有效地处理 I/O 操作。然而,当它遇到任何计算量大的操作时,它会导致主事件循环冻结。

在这里插入图片描述
当 Node.js 发现异步操作时,它将其“离岸”到线程池。但是,当它需要运行计算量大的操作时,它会在其主线程上执行该操作,这会导致应用程序阻塞,直到操作完成。因此,为了缓解这个问题,Node.js 引入了工作线程的概念,以帮助从主事件循环中卸载 CPU 密集型操作,以便开发人员可以以非阻塞方式并行生成多个线程。

它通过启动一个隔离的 Node.js 上下文来实现此目的,该上下文包含自己的 Node.js 运行时、事件循环和事件队列,该上下文在远程 V8 环境中运行。这在与主事件循环断开连接的环境中执行,从而允许主事件循环释放。

在这里插入图片描述
如上所示,Node.js 创建独立的运行时作为工作线程,其中每个线程独立于其他线程执行,并通过消息通道将其进程状态传达给父线程。这允许父线程继续照常执行其功能(不会被阻塞)。通过这样做,我们可以在 Node.js 中实现多线程。

在 Node.js 中使用工作线程有什么好处

正如我们所看到的,使用工作线程对于 CPU 密集型应用程序非常有益。事实上,它有几个优点:

  • 性能改进:我们可以将计算繁重的操作转移到工作线程,这可以释放主线程,从而使我们的应用程序能够响应更多请求。
  • 提高并行性:如果我们有一个大型进程,希望将其分成子任务并并行执行,则可以使用工作线程来执行此操作。例如,如果我们要确定 1,999,3241,123 是否为质数,则可以使用工作线程检查范围内的除数 -(WT1 中为 1 到 100,000,WT2 中为 100,001 到 200,000,等等)。这将加快我们的算法并导致更快的响应。

什么时候应该在 Node.js 中使用工作线程

如果我们考虑一下,我们应该只使用工作线程来运行与父线程隔离的计算密集型操作。

在工作线程中运行 I/O 操作是没有意义的,因为它们已经被转移到事件循环中。因此,当我们需要在隔离环境中执行计算量大的操作时,请考虑使用工作线程。

如何在 Node.js 中构建工作线程

如果所有这些听起来对我们很有吸引力,那么让我们看看如何在 Node.js 中实现工作线程。考虑下面的代码片段:

const {
  Worker,
  isMainThread,
  parentPort,
  workerData,
} = require("worker_threads");

const { generatePrimes } = require("./prime");

const threads = new Set();
const number = 999999;

const breakIntoParts = (number, threadCount = 1) => {
  const parts = [];
  const chunkSize = Math.ceil(number / threadCount);

  for (let i = 0; i < number; i += chunkSize) {
    const end = Math.min(i + chunkSize, number);
    parts.push({ start: i, end });
  }

  return parts;
};

if (isMainThread) {
  const parts = breakIntoParts(number, 5);
  parts.forEach((part) => {
    threads.add(
      new Worker(__filename, {
        workerData: {
          start: part.start,
          end: part.end,
        },
      })
    );
  });

  threads.forEach((thread) => {
    thread.on("error", (err) => {
      throw err;
    });
    thread.on("exit", () => {
      threads.delete(thread);
      console.log(`Thread exiting, ${threads.size} running...`);
    });
    thread.on("message", (msg) => {
      console.log(msg);
    });
  });
} else {
  const primes = generatePrimes(workerData.start, workerData.end);
  parentPort.postMessage(
    `Primes from - ${workerData.start} to ${workerData.end}: ${primes}`
  );
}

上面的代码片段展示了一个可以利用工作线程的理想场景。要构建工作线程,我们需要从库中导入Worker、IsMainThread、parentPort和 workerDataworker_threads 。这些定义将用于创建工作线程。

上面的代码创建了一种算法,可以查找给定范围内的所有素数。它将主线程中的范围分成不同的部分(上面示例中的五个部分),然后使用 new Worker() 来创建一个工作线程来处理每个部分。工作线程执行 else块,该块在分配给该工作线程的范围内查找素数,并最终使用 parentPort.postMessage() 将结果发送回父(主)线程。

Node.js 中的子进程是什么

子进程与工作线程不同。虽然工作线程在同一进程中提供隔离的事件循环和 V8 运行时,但子进程是整个 Node.js 运行时的单独实例。每个子进程都有自己的内存空间,并通过消息流或管道(或文件、数据库、TCP/UDP 等)等 IPC(进程间通信)技术与主进程进行通信。

在 Node.js 中使用子进程有什么好处

在 Node.js 应用程序中使用子进程会带来很多好处:

  • 改进的隔离性:每个子进程都在自己的内存空间中运行,提供与主进程的隔离。这对于可能存在资源冲突或需要分离的依赖性的任务是有利的。
  • 提高可扩展性:子进程在多个进程之间分配任务,这使我们可以利用多核系统并处理更多并发请求。
  • 提高鲁棒性:如果子进程由于某种原因崩溃,它不会随之崩溃我们的主进程。
  • 运行外部程序:子进程允许我们将外部程序或脚本作为单独的进程运行。这对于需要与其他可执行文件交互的场景非常有用。

什么时候应该在 Node.js 中使用子进程

所以,现在我们知道子进程给图片带来的好处了。了解何时应该在 Node.js 中使用子进程非常重要。根据我的经验,当我们想在 Node.js 中执行外部程序时,我建议我们使用子进程。

比如存在一种使用场景:我们必须从 Node.js 服务中运行外部可执行文件。不可能在主线程内执行二进制文件。因此,我们必须使用一个子进程来执行二进制文件。

如何在 Node.js 中构建子进程

在 Node.js 中创建子进程的方法有多种,可以使用 spawn() 、fork()、exec() 与 execFile() 等方法。

const { spawn } = require('child_process');
const child = spawn('node', ['child.js']);

child.stdout.on('data', (data) => {
  console.log(`Child process stdout: ${data}`);
});

child.on('close', (code) => {
  console.log(`Child process exited with code ${code}`);
});

我们所要做的就是从child_process模块导入一个 spawn() 方法,然后通过传递 CLI 参数作为参数来调用该方法。

如何在工作线程和子进程之间进行选择

现在我们知道什么是子进程和工作线程,那么了解何时使用这些技术很重要。它们都不是适合所有情况的灵丹妙药。这两种方法都适用于特定条件。

在以下情况下使用工作线程:

  • 我们正在运行 CPU 密集型任务。如果我们的任务是 CPU 密集型的,那么工作线程是一个不错的选择。
  • 我们的任务需要线程之间的共享内存和高效通信。工作线程具有对共享内存和用于通信的消息系统的内置支持。

在以下情况下使用子进程:

  • 我们正在运行需要隔离并独立运行的任务,特别是当它们涉及外部程序或脚本时。每个子进程都在自己的内存空间中运行。
  • 我们需要使用 IPC 机制(例如标准输入/输出流、消息传递或事件)在进程之间进行通信。子进程非常适合此目的。

总结

并行处理正在成为现代系统设计的一个重要方面,特别是在构建处理非常大的数据集或计算密集型任务的应用程序时。因此,在使用 Node.js 构建此类应用程序时,考虑工作线程和子进程非常重要。

如果我们的系统没有采用正确的并行处理技术进行正确设计,我们的系统可能会因过度耗尽资源而表现不佳(因为生成这些资源也会消耗大量资源)。

因此,对于软件工程师和架构师来说,清楚地验证需求并根据本文中提供的信息选择正确的工具非常重要。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Node.js 工作线程与子进程:应该使用哪一个 的相关文章

随机推荐

  • ES6 面试题 | 13.精选 ES6 面试题

    前端开发工程师 主业 技术博主 副业 已过CET6 阿珊和她的猫 CSDN个人主页 牛客高级专题作者 在牛客打造高质量专栏 前端面试必备 蓝桥云课签约作者 已在蓝桥云课上架的前后端实战课程 Vue js 和 Egg js 开发企业级健康管理
  • 时序预测 | Python实现GRU-XGBoost组合模型电力需求预测

    时序预测 Python实现GRU XGBoost组合模型电力需求预测 目录 时序预测 Python实现GRU XGBoost组合模型电力需求预测 预测效果 基本描述 程序设计 参考资料
  • DeepCache

    这里介绍一种名为DeepCache的无需训练的方法 它利用了去噪过程中模型深层特征的相似性 通过缓存 Cache 来避免重新计算网络中的深层特征 仅计算网络的浅层 从而减少计算量 实验展示了DeepCache相较于需要重新训练的现有剪枝和蒸
  • 时序预测 | Python实现LSTM-Attention电力需求预测

    时序预测 Python实现LSTM Attention电力需求预测 目录 时序预测 Python实现LSTM Attention电力需求预测 预测效果 基本描述 程序设计 参考资料
  • 时序预测 | Python实现LSTM-Attention-XGBoost组合模型电力需求预测

    时序预测 Python实现LSTM Attention XGBoost组合模型电力需求预测 目录 时序预测 Python实现LSTM Attention XGBoost组合模型电力需求预测 预测效果 基本描述 程序设计 参考资料
  • 目标检测YOLO系列从入门到精通技术详解100篇-【目标检测】机器视觉(基础篇)(十三)

    目录 前言 几个高频面试题目 人工智能 机器学习 计算机视觉之间的关系 知识储备 计算机视觉 计算机图像学 图像处理基础知识 光学棱镜
  • 工业缺陷检测~

    这里介绍算法在图像处理中的应用 同时还介绍了常用的图像处理算法和现有可用的视觉检测软件库 文章旨在帮助读者更好地了解算法在图像处理中的应用 提高图像处理的效果和效率 算法 预处理算法 检测算法 常用的图像处理算法 1 图像变换 空域与频域
  • WPF-UI HandyControl 控件简单实战

    文章目录 前言 UserControl简单使用 新建项目 直接新建项目 初始化UserControl Geometry 矢量图形 额外Icon导入
  • 关于“Python”的核心知识点整理大全24

    10 1 6 包含一百万位的大型文件 前面我们分析的都是一个只有三行的文本文件 但这些代码示例也可处理大得多的文件 如果我们有一个文本文件 其中包含精确到小数点后1 000 000位而不是30位的圆周率值 也可 创建一个包含所有这些数字的字
  • 知识图谱之关键实体数据爬取

    目录 爬取实体概览 爬取技术介绍 requests html Selenium 两者比较 学习路径 代码结构 高可用爬取策略 基于文件记录位点 请求失败指数退避重试 爬取代码 品牌数据 车系数据 车型数据 车型配置数据 代码地址 爬取实体概
  • 在Windows上通过cmake-gui及VS2019来 编译OpenCV-4.5.3源码

    文章目录 下载OpenCV 4 5 3源码 下载opencv contrib 4 5 3源码 打开cmake gui 选择生成器 通过 Visual Studio 2019 打开构建好的 sln工程文件 执行编译操作 执行安装操作
  • 屏幕超时休眠-Android13

    屏幕超时休眠 Android13 1 设置界面 1 2 属性值 1 2 1 默认值 1 2 2 最小值限制 1 3 属性值疑问 Settings System SCREEN OFF TIMEOUT 2 超时灭屏
  • OSG中几何体的绘制(一)

    本章主要介绍一些几何体的绘制方法 绘制几何体在场景中是非常常见的 也是最基本的 在很多应用程序中可以看到相当复杂的场景 但不管场景有多复杂 它们都是由少数几个基本的图形元素构建而成的 只要想想达芬奇那些伟大的作品也是由铅笔和画刷所完成的 读
  • xtcocotools 安装 mmcv

    目录 xtcocotools 2023测试成功 mmcv安装方法 xtcocotools 2023测试成功 pip install xtcocotools mmcv安装方法 pip install U openmim mim install
  • 星纵物联2024届秋招/校招内推信息/内推码

    公司名称 星纵物联 内推码 ESVMA3 内推来源 内推鸭小程序 官方招聘网站 厦门星纵物联招聘官网
  • yyy888

    8
  • MyBatis中的MapperScan的作用是干什么的?

    MapperScan 是 MyBatis Plus 提供的注解 它的作用是扫描指定包下的所有接口 将其注册成 MyBatis 的 Mapper 在 MyBatis Plus 中 它是用于替代原生 MyBatis 中 XML 配置文件中的
  • HarmonyOS(十四)——状态管理之@State装饰器(组件内状态)

    前言 在 初识状态管理 我们了解了状态管理的基本概念 以及管理组件拥有的状态有哪几种装饰器 今天我们就来认识一下第一种装饰器 State装饰器 组件内状态 概述 State装饰的变量 或称为状态变量 一旦变量拥有了状态属性 就和自定义组件的
  • LeetCode经典150题Golang版.121. 买卖股票的最佳时机

    题目 121 买卖股票的最佳时机 给定一个数组 prices 它的第 i 个元素 prices i 表示一支给定股票第 i 天的价格 你只能选择 某一天 买入这只股票 并选择在 未来的某一个不同的日子 卖出该股票 设计一个算法来计算你所能获
  • Node.js 工作线程与子进程:应该使用哪一个

    Node js 工作线程与子进程 应该使用哪一个 并行处理在计算密集型应用程序中起着至关重要的作用 例如 考虑一个确定给定数字是否为素数的应用程序 如果我们熟悉素数 我们就会知道必须从 1 遍历到该数的平方根才能确定它是否是素数 而这通常非