Electron 多线程实践指南(上)— 初识 worker_threads

大家好,我是徐徐。今天我们讲讲如何在 Electorn 中进行多线程操作,先来认识一下 worker_threads 。

前言

我们在开发一个应用的时候可能会遇到一些比较特殊的场景,比如数据的大量计算, 图片处理和视频编码 , 密集型加密/解密操作等一些 CPU 密集型任务,这些非常规的动作可能会阻塞整个程序,使得整个应用变得卡顿。为了提升应用的响应速度和用户体验,我们需要将这些耗时操作放到线程中去执行,从而避免阻塞整个进程。下面我们就来看看如何在 Electorn 中进行多线程的操作吧。

Worker Threads 基础

worker_threads 模块允许使用并行执行 JavaScript 的线程,工作线程对于执行 CPU 密集型的 JavaScript 操作很有用。Worker 对 I/O 密集型的工作帮助不大,Node.js 内置的异步 I/O 操作比工作线程更高效。与 child_process 或 cluster 不同,worker_threads 可以共享内存,它们通过传输 ArrayBuffer 实例或共享 SharedArrayBuffer 实例来实现。

用一段代码看看 worker_threads

javascript 复制代码
// 从 worker_threads 模块引入 Worker 类
improt { Worker } from "worker_threads";

// 创建一个新的工作线程
// path: 工作线程脚本的路径
// data: 传递给工作线程的数据
const worker = new Worker(path, { data });

// 监听工作线程发送的消息
worker.on('message', msg => {
    // msg 是工作线程通过 parentPort.postMessage() 发送的数据
});

// 监听工作线程中的错误
worker.on('error', err => {
    // 处理工作线程中抛出的错误
});

// 监听工作线程结束事件
worker.on('exit', exitcode => {
    // exitcode: 0 表示正常退出,非 0 表示异常退出
});

// 监听工作线程开始运行事件
worker.on('online', () => {
    // 当工作线程开始执行时触发
});

这段代码展示了工作线程的四个主要事件

  • message:接收工作线程传来的数据
  • error:处理工作线程的错误
  • exit:处理工作线程的退出
  • online:工作线程启动就绪

通信

如果此线程是 Worker,则这是允许与父进程通信的 MessagePort。使用 parentPort.postMessage() 发送的消息在使用 worker.on('message') 的父进程中可用,使用 worker.postMessage() 从父进程发送的消息在使用 parentPort.on('message') 的该线程中可用。

javascript 复制代码
import { Worker, isMainThread, parentPort } from "worker_threads";

if (isMainThread) {
  const worker = new Worker(__filename);
  worker.once('message', (message) => {
    console.log(message);  // 打印 'Hello, world!'.
  });
  worker.postMessage('Hello, world!');
} else {
  // 收到消息之后发送回去
  parentPort.once('message', (message) => {
    parentPort.postMessage(message);
  });
}

实际应用例子

上面讲到的基本都是 Node.js 官网所写的,我们这里展示一个实际应用的例子来看看 Node 多线程的应用,这里必须要要拿出斐波那契数列这个例子了,因为它非常耗费 CPU,计算量很大,我们就拿它来做实验。

我们需要创建一个 worker 文件,这个 worker 文件里面就是你的各种处理逻辑,如下,这里我们的处理逻辑就是 getFibonacciNumber,当然你可以把这个方法换成其他的非常耗时、非常耗费 CPU 的函数。

  • src/worker/fibonacci.work.js
javascript 复制代码
import { parentPort, workerData,isMainThread } from "worker_threads";

if (isMainThread) {
  console.log('Main thread');
} else {
  parentPort.postMessage(getFibonacciNumber(workerData.num))
}


export function getFibonacciNumber (num) {
  if (num === 0) {
    return 0;
  } else if (num === 1) {
    return 1;
  } else {
    return getFibonacciNumber(num - 1) + getFibonacciNumber(num - 2);
  }
}

有了 worker 脚本,我们需要在主进程中去调用,然后也把不调用线程的方法写在下面好做比较,不然怎么知道多线程有多香呢。

  • src/main/worker/index.ts
javascript 复制代码
import { Log4 } from "@/common/log";
import { Worker } from "worker_threads";
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
import  {getFibonacciNumber } from "@/worker/fibonacci.work"

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

export const testFbonacciWorker = (number:Number) => {
  console.time(`workerTime ${number}`);
  const worker = new Worker(resolve(__dirname, '../worker/fibonacci.work.js'), { workerData: { num: number } });
  worker.on("message", result => {
    Log4.info (`${number}th Fibonacci Result: ${result}`);
    console.timeEnd(`workerTime ${number}`)
  });

  worker.on("error", error => {
    Log4.error(error);
  });

  worker.on("exit", exitCode => {
    Log4.info(`结束 Code ${exitCode}`);
  })
}

export const testMainThreadBlocking = () => {
  console.time('blockingTest');
  let sum = 0;
  for (let i = 0; i < 10000000000000000000000; i++) {
    sum += i;
  }
  console.timeEnd('blockingTest');
}


export const runFbonacciWorker = () => {
  Log4.info('runFbonacciWorker start')
  Log4.info('runFbonacciWorker 45')
  testFbonacciWorker(45)
  Log4.info('runFbonacciWorker 10')
  testFbonacciWorker(10)
  testMainThreadBlocking()
}

export const testGetFibonacciNumberWithoutWork = () => {
  console.time('testGetFibonacciNumberWithoutWork')
  Log4.info('testGetFibonacciNumber start')
  const result1 = getFibonacciNumber(45)
  Log4.info(`45th Fibonacci Result: ${result1}`)

  const result2 = getFibonacciNumber(10)
  Log4.info(`10th Fibonacci Result: ${result2}`)
  
  Log4.info('testGetFibonacciNumber')
  console.timeEnd('testGetFibonacciNumberWithoutWork')
  testMainThreadBlocking()
}

在界面上写两个按钮分别触发一下两个方法,比较一下用了 worker_threads 和没用 worker_threads 的效果。

  • 使用了 worker_threads ,主进程不会出现阻塞情况,异步回调机制。
  • 没有使用 worker_threads ,主进程会出现阻塞情况,并且界面会出现严重卡顿,导致界面未响应。

结语

这里我们只是简单得认识了一下 Node 中的 worker_threads,它是 Node 多线程的基础,在较为复杂的应用中都可能会使用到。不过简单的使用可能还并不能满足一些特定需求,下一节我们将更加深入得讨论它,涉及线程池、内存共享、线程异常处理等知识体系,并结合实际场景做出相应的演示。

相关推荐
QQ1__81151751514 分钟前
Spring boot名城小区物业管理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
前端·vue.js·spring boot
钛态15 分钟前
前端微前端架构:大项目的救命稻草还是自找麻烦?
前端·vue·react·web
一粒黑子16 分钟前
【实战解析】阿里开源 PageAgent:纯前端 GUI Agent,一行JS让网页支持自然语言操控
前端·javascript·开源
独角鲸网络安全实验室18 分钟前
2026微信小程序抓包全解析:从实操落地到合规风控,解锁前端调试新范式
前端·微信小程序·小程序·抓包·系统代理绕过·https证书严格校验·进程隔离
紫微AI19 分钟前
前端文本测量成了卡死一切创新的最后瓶颈,pretext实现突破了
前端·人工智能·typescript
GISer_Jing19 分钟前
AI前端(From豆包)
前端·aigc·ai编程
IT枫斗者19 分钟前
前端部署后如何判断“页面是不是最新”?一套可落地的版本检测方案(适配 Vite/Vue/React/任意 SPA)
前端·javascript·vue.js·react.js·架构·bug
测试修炼手册19 分钟前
[测试技术] 深入理解 JSON Web Token (JWT)
前端·json
AI老李21 分钟前
2026 年 Web 前端开发的 8 个趋势!
前端
里欧跑得慢23 分钟前
15. Web可访问性最佳实践:让每个用户都能平等访问
前端·css·flutter·web