编程深水区之并发③:Node.js的并发编程

在Node里耍多线程和多进程,会不会闪到腰?!

一、Node和JS的关系

  1. Node是JS的运行环境。最初JS只在浏览器中运行,它依赖于浏览器的JS引擎(如Chrome的V8、Firefox的SpiderMonkey)。Node从Chrome中获得灵感,并直接套用了V8引擎,让JS可以在服务器端运行,意味着可以使用JS来编写控制台应用、创建API和构建网络服务器。
  2. Node的核心部分使用C++编写,比如用于解析和执行JS的V8引擎(就是Chrome中的V8引擎),用于操作异步任务的libuv库,以及许多核心库(如fs、net、stream、worker_thread、child_process等)。Node将这些使用C++编写的底层功能,包装成JS接口,使我们可以使用JS来编写应用程序。
  3. 我们使用Vue或者React开发前端应用时,都要安装Node。调试运行时,实际上就是使用Node作为开发时服务器,调试时网站运行在本地Node服务器上,编译、打包和发布工作,则由Webpack或Vite来完成。
  4. JS的确是单线程,它处理并发的机制是非阻塞的事件循环机制。但Node的核心功能是C++编写的,自然支持创建多线程或多进程来完成异步操作

二、Node多线程

Node提供worker_threads模块,用于手动创建多线程。之前有介绍用于异步操作的libuv库,它主要作用于Node单线程事件循环机制中的异步操作,主线程碰到异步任务时,会把它扔给libuv,libuv完成后扔到任务队列里。libuv的高效,源于它基于线程池,这有些类似于C#的TPL了,后面章节再细说。
worker_threads并不依赖于libuv,它直接依赖于底层操作系统的线程实现,在每个Worker中,有独立的V8引擎和上下文。这至少意味着:(1)创建线程的代价是比较高的;(2)线程之间相互独立,需要依靠消息机制通讯,通讯开销会比较大。所以,没事别乱开线程。

2.1 worker_thread的常用API

  • Worker类:用来创建和管理Worker线程。一个Worker线程可以执行与主线程并行的任务。
  • isMainThread:一个布尔值,指示代码是否在主线程中运行。
  • parentPort:这是主线程与工作线程之间的通信通道。
  • workerData:这是传递给Worker线程的初始数据。可以在创建Worker时传递,并在Worker中访问。
  • MessageChannel:用于创建两个相互连接的通信端口,允许在不同线程之间进行通信。
  • SharedArrayBuffer/Atomics:允许在多个线程之间共享数据时,并进行安全的原子操作。

2.2 主线程和Worker线程通讯

//main.js
//以下文件包含了主线程和Worker线程的代码,通过isMainThread来判断
//实际开发中,一般将Worker线程的代码放到单独的JS文件中
const {
    Worker, isMainThread, parentPort, workerData }
  = require('worker_threads');

if (isMainThread) {
    // 以下代码在主线程中=======================
  
  // 创建一个新的Worker线程
  // __filename指向当前文件路径,表示Worker也在当前文件中
  //workerData,用于向Worker线程传递数据
  const worker = new Worker(__filename, {
   
    workerData: {
    start: 1, end: 100 }
  });

  //主线程监听Worker线程,message、error和exit是事件名称
  //监听从Worker线程发来的消息
  //result为Worker线程中postMessage出来的值
  worker.on('message', result => {
   
    console.log(`Result from worker: ${
     result}`);
  });
  //监听Worker线程中的错误
  worker.on('error', error => {
   
    console.error(`Worker error: ${
     error}`);
  });
  //监听Worker线程的结束状态
  worker.on('exit', code => {
   
    if (code !== 0) {
   
      console.error(`Worker stopped with exit code ${
     code}`);
    } else {
   
      console.log('Worker finished successfully');
    }
  });

} else {
    // 以下代码在Worker线程中============================
  
  // 访问workerData
  const {
    start, end } = workerData;

  // 简单的计算任务:计算[start, end]范围内的和
  let sum = 0;
  for (let i = start; i <= end; i++) {
   
    sum += i;
  }

  // 发送结果给主线程
  parentPort.postMessage(sum);
}

2.3 Worker线程之间通过MessageChannel通讯

//main.js,主线程================================================
const {
    Worker, MessageChannel } = require('worker_threads');
const path = require('path');

// 创建一个新的MessageChannel,解构出两个通讯端口
const {
    port1, port2 } = new MessageChannel();

// 创建第一个Worker
const worker1 = new Worker(path.resolve(__dirname, 'worker1.js'));

// 创建第二个Worker
const worker2 = new Worker(path.resolve(__dirname, 'worker2.js'));

// 将port1发送给worker1,port2发送给worker2
worker1.postMessage({
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值