前言

前两天我们介绍了使用 Nodejs 中的 child_process 模块创建多个子进程,同时利用进程间通信的API构建了一个集群式的Web服务器。实际上,你可以通过 cluster 模块更方便的完成这一操作。

但是,cluster 创建的进程之间无法共享内存,通信必须使用 JSON 格式,有一定的局限性和性能问题。如果你不想要进程隔离,可以使用 worker_thread 模块,它允许在一个 Node.js 实例中运行多个应用程序线程。相比创建多个进程更轻量,并且可以共享内存。

进程间通过传输 ArrayBuffer 实例或共享 SharedArrayBuffer 实例来做到这一点,对数据格式没有太多要求。但是要注意,数据中不能包含函数。

Cluster 多进程

我们可以使用 cluster 模块提供的API重构昨天的案例:

// master.js
const cl = require("cluster");
const cpus = require("os").cpus().length;
// 修改默认的 fork() 方法配置
cl.setupPrimary({
  exec: 'worker.js'
});
for(let i = 0; i < cpus; i  ) {
  cl.fork();
};
cl.on('listening', (data) => {
  console.log(`listenning on: ${data.id}--${data.process.pid}`);
});
cl.on('exit', (data, code, signal) => {
  console.log(`exited: ${data.id}--${data.process.pid}, kill code: $[code], signal: ${signal}`);
  cl.fork();
});

子进程依旧使用昨天的代码:

const http = require("http");
const server = http.createServer((req, res) => {
  res.writeHead(200, {
    "Content-Type": "text/plain"
  });
  res.end("Hello,World!"   process.pid);
  // 抛出异常,捕获后终止进程
  throw new Error('throw exception');
}).listen(1337);
// 捕获异常后终止进程
process.on('uncaughtException', (err) => {
  // 停止接收新的连接
  server.close((data) => {
    console.log(`worker: ${process.pid} is stopping!`);
    process.exit(1);
  })
  // 避免长连接请求长时间无法终止,5s后自动终止
  setTimeout(() => {
    process.exit(1);
  }, 5000)
});

执行 node master.js,会得到与昨天利用 child_process 模块创建子进程集群相同的效果。

同样,你可以使用官方推荐的写法,利用 cluster.isPrimary 和 cluster.isWorker 来判断当前进程是否为主进程:

const cluster = require('node:cluster');
const http = require('node:http');
const numCPUs = require('node:os').cpus().length;
const process = require('node:process');
if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);
  // Fork workers.
  for (let i = 0; i < numCPUs; i  ) {
    cluster.fork();
  }
  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
  });
} else {
  // Workers can share any TCP connection
  // In this case it is an HTTP server
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(1337);
  console.log(`Worker ${process.pid} started`);
};

实现原理

事实上,cluster 模块就是将 child_processnet 模块的API组合起来实现的。cluster启动时,进程会在内部启动TCP服务器。而在调用 cluster.fork() 复制子进程时,会将这个TCP服务器端 Socket 的句柄发送给工作进程。如果进程是通过 cluster.fork() 复制出来的,那么它的环境变量里就存在 NODE_UNIQUE_ID。如果工作进程中存在 listen() 侦听网络端口的调用,它将拿到该句柄,再通过 SO_REUSEADDR 端口重用,从而实现多个子进程共享端口。对于正常方式启动的进程,则不存在句柄共享和传递等过程。

cluster 内部隐式创建TCP服务器的方式对使用者是透明的,你不需要自己手动去实现句柄的传递,但也正是因此,它无法像使用 child_process 那样灵活。在 child_process 中你可以自行控制句柄的传送,因此可以灵活地控制工作进程,甚至控制多组工作进程。

cluster事件

  • Event: disconnect 主进程和工作进程之间IPC通道断开后会触发该事件。
  • Event: exit 有工作进程退出时触发该事件。
  • Event: fork 复制一个工作进程后触发该事件。
  • Event: listening 工作进程中调用 listen() 后,发送该消息给主进程,主进程收到后,触发该事件。
  • Event: message
  • Event: online fork好一个工作进程后,工作进程主动发送该消息给主进程,主进程收到消息后,触发该事件。
  • Event: setup .setupPrimary() 方法执行后触发

💡 这些事件大多跟 child_process 模块的事件相关,在进程间消息传递的基础上完成的封装。

使用 Node 构建集群能够充分利用多核CPU的计算性能,而 child_process 模块的进程间通信和多种事件能够极大提升Node的稳定性。但进程间无法共享资源,进程间通信有局限性和性能问题。此时就需要引入更轻量级的线程了。

Worker threads多线程

V8 多线程模型

众所周知,JavaScript 在运行时是单线程的。但 JavaScript 的 Runtime V8 引擎却不是单线程的。大致包括以下几个线程:

  • JavaScript 主线程:编译、执行代码。
  • 编译线程:当主线程在执行时,编译线程可以优化代码。
  • Profiler 线程:记录方法耗时的线程。
  • 其它线程:比如支持并行 GC 的多线程。
  • libuv线程池,默认四个线程,全局共享,可以将异步操作和计算密集任务交给它执行。

对于 Node 来说,crypto 这种 CPU 密集 和 fs 这种 I/O 密集的任务是在 libuv线程池 中进行的。其执行模型是单独创建一个进程,在这个进程中同步执行任务,然后将结果返回到 Event Loop 中,Event Loop 可以通过回调函数获取并使用结果。

const fs = require("fs");
fs.writeFile('./target.txt', 'hello Node.js', (err) => {
  if (err) throw err;
  console.log('文件已被保存');
});

使用非阻塞方法,长耗时的方法不会阻塞主进程之后的代码,只需告诉 Worker Pool 去执行该命令,并将结果返回给预先设置好的回调函数,在计算完成时触发即可。

💡 由于 Worker Pool 运行在 libuv线程池 中,主线程的 Event Loop 不会被阻塞。能够充分利用 CPU 资源。

多线程支持

Node v10.5.0 提供了 Worker threads 模块,开始支持多线程编程。在创建出的每个工作线程中,都会包含 V8 和 libuv,即都包含Event Loop:

你可以通过下面这段简单的代码来体验一下:

// main.js
const { Worker, isMainThread } = require('worker_threads');
if (isMainThread) {
  console.log("I'm main thread: ", isMainThread);
  // create subThread
  new Worker(__filename);
}
else {
  console.log("I'm not main thread: ", isMainThread);
  // subThread destroy
}

我们在主线程中调用new方法创建了一个子线程,子线程执行完自动销毁。最后执行结果如下:

💡 合理使用子线程,你能充分调用和分配资源。对于有计算密集型需求的应用,这是一个重要的优化手段。另外,由于频繁地创建、销毁一个线程的开销很大,你可以创建线程池来解决这个问题。

总结

通过构建集群,你能够充分调用CPU资源,赋予Node更强劲的性能。而利用多线程模型,将长耗时的任务交由子线程来处理,你能合理分配程序运行资源。

目前为止,我们介绍完了 Node 的网络、IO、进程模块,还剩下异步编程和Event Loop两个重点。另外,今天在看 Node 文档时发现 Node v19 刚刚发布了,v18 即将成为稳定版

以上就是Nodejs 构建Cluster集群多线程Worker threads的详细内容,更多关于Nodejs 构建Cluster多线程的资料请关注Devmax其它相关文章!

Nodejs 构建Cluster集群多线程Worker threads的更多相关文章

  1. iOS:核心图像和多线程应用程序

    我试图以最有效的方式运行一些核心图像过滤器.试图避免内存警告和崩溃,这是我在渲染大图像时得到的.我正在看Apple的核心图像编程指南.关于多线程,它说:“每个线程必须创建自己的CIFilter对象.否则,你的应用程序可能会出现意外行为.”这是什么意思?我实际上是试图在后台线程上运行我的过滤器,所以我可以在主线程上运行HUD(见下文).这在coreImage的上下文中是否有意义?

  2. ios – 意外的核心数据多线程违规

    我正在使用苹果的并发核心数据调试器.-com.apple.CoreData.ConcurrencyDebug1有时候我得到__Multithreading_Violation_AllThatIsLeftToUsIsHonor__,即使我几乎肯定线程没有被违反.这是发生异常的代码的一部分(代码是扩展NSManagedobject的协议的一部分):代码在上下文的执行:块中执行.这里是线程信息:和调试器

  3. ios – UIGraphicsBeginImageContextWithOptions和多线程

    我对UIGraphicsBeginImageContextWithOptions和线程有点困惑,因为根据UIKitFunctionReferenceUIGraphicsBeginImageContextWithOptions应该只在主线程上调用.当被调用时,它创建一个基于位图的上下文,可以使用CoreGraphics的函数或者像-drawInRect这样的方法来处理:对于UIImage,-draw

  4. Swift之dispatch_source实现多线程定时关闭功能

    由于在项目中需要用到定时关闭音频功能,本来打算用NSTimer的,可是写起来并不是那么精简好用,所以又在网上找到相关的实例,结合自己项目需要,就写出了如下代码,还请大家指教,废话不多说:

  5. swift 多线程实现

  6. swift_多线程基础_最简单用法GCD, NSOperationQueue, NSThread

    ////ViewController.swift//study1-1//Createdbyadminon15/12/28.//copyright2015年admin.Allrightsreserved.//importUIKitclassViewController:UIViewController{@IBOutletvarmyLable:UILabel?@IBActionfuncclickBut

  7. swift__多线程GCD详解

    有以下*-disPATCH_QUEUE_PRIORITY_HIGH:*-disPATCH_QUEUE_PRIORITY_DEFAULT:多用默认*-disPATCH_QUEUE_PRIORITY_LOW:*-disPATCH_QUEUE_PRIORITY_BACKGROUND:*第二个参数为预留参数,一般为0*/letmyQueue:dispatch_queue_t=dispatch_get_global_queue//用异步的方式运行队列里的任务dispatch_async//-------------

  8. Swift - 多线程实现方式3 - Grand Central DispatchGCD

    dispatchqueue可以是并发的或串行的。dispatch_suspend后,追加到DispatchQueue中尚未执行的任务在此之后停止执行。6//创建并行队列conQueue:dispatch_queue_t=dispatch_queue_create//暂停一个队列dispatch_suspend//继续队列dispatch_resume6,dispatch_once一次执行保证dispatch_once中的代码块在应用程序里面只执行一次,无论是不是多线程。注意,我们不能(直接)取消我们已经提

  9. 【Swift】三种多线程处理方式

    )Threadbtn.frame=CGRectMakeThreadbtn.setTitle//普通状态下的文字Threadbtn.setTitle//触摸状态下的文字letmethod:Selector=methodarr[index!]Threadbtn.addTargetself.view.addSubview;}}overridefuncdidReceiveMemoryWarning(){super.didReceiveMemoryWarning()}//1.NSThread线程functestNS

  10. Swift多线程之GCD

    学自:http://www.jianshu.com/p/2598a4e9c139

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部