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

相关推荐
diemeng111912 分钟前
AI前端开发技能变革时代:效率与创新的新范式
前端·人工智能
bin91532 小时前
DeepSeek 助力 Vue 开发:打造丝滑的复制到剪贴板(Copy to Clipboard)
前端·javascript·vue.js·ecmascript·deepseek
晴空万里藏片云4 小时前
elment Table多级表头固定列后,合计行错位显示问题解决
前端·javascript·vue.js
曦月合一4 小时前
html中iframe标签 隐藏滚动条
前端·html·iframe
奶球不是球4 小时前
el-button按钮的loading状态设置
前端·javascript
kidding7234 小时前
前端VUE3的面试题
前端·typescript·compositionapi·fragment·teleport·suspense
Σίσυφος19006 小时前
halcon 条形码、二维码识别、opencv识别
前端·数据库
学代码的小前端6 小时前
0基础学前端-----CSS DAY13
前端·css
css趣多多7 小时前
案例自定义tabBar
前端
姑苏洛言8 小时前
DeepSeek写微信转盘小程序需求文档,这不比产品经理强?
前端