WEBKT

Node.js Worker Threads 进阶:解锁复杂同步协作的终极秘籍

40 0 0 0

为什么需要 Worker Threads?

Worker Threads 的基本用法回顾

创建 Worker

worker.js (worker 线程)

Worker Threads 的同步机制:EventEmitter 方案

核心概念:事件驱动

使用 EventEmitter 实现 Worker Threads 同步

示例:使用主线程的 EventEmitter

示例:使用 worker 线程的 EventEmitter

EventEmitter 方案的优缺点

Worker Threads 的同步机制:自定义事件方案

核心概念:事件定义

使用自定义事件实现 Worker Threads 同步

示例:自定义事件方案

自定义事件方案的优缺点

其他同步机制和高级技巧

1. 共享内存 (SharedArrayBuffer)

示例:使用 SharedArrayBuffer

2. 锁 (Lock)

示例:使用第三方锁库 (例如 async-mutex)

3. 消息队列 (Message Queue)

示例:使用第三方消息队列库 (例如 bull)

4. 信号量 (Semaphore)

5. 避免死锁 (Deadlock)

实际项目中的应用场景

1. 并行处理大规模数据

示例:并行处理图像

2. 后台任务处理

示例:后台发送邮件

3. 构建高性能 Web 服务器

4. 数据库操作

总结

扩展阅读

大家好,我是老码农!

今天,我们来聊聊 Node.js 中一个非常强大的特性——Worker Threads。尤其对于那些已经熟悉 Node.js 异步编程,并希望进一步优化多核 CPU 利用率,构建高性能应用的开发者来说,Worker Threads 无疑是一把利器。这次,我们不满足于简单的“Hello World”,而是要深入探讨 Worker Threads 之间的复杂同步机制,比如如何利用 EventEmitter 或者自定义事件来实现线程间的精细化协作,同时分析不同方案的优缺点,让你在实际项目中游刃有余。

为什么需要 Worker Threads?

在深入同步机制之前,我们先简单回顾一下为什么我们需要 Worker Threads。Node.js 的单线程事件循环模型在处理 I/O 密集型任务时表现出色,但当遇到 CPU 密集型任务时,例如图像处理、数据压缩、复杂的计算等,就会出现问题。因为 JavaScript 引擎只有一个线程,当这个线程被 CPU 密集型任务阻塞时,整个 Node.js 应用的响应都会变慢,甚至卡死。

Worker Threads 的出现,就是为了解决这个问题。它允许你在 Node.js 中创建多个独立的 JavaScript 线程,每个线程拥有自己的 V8 引擎实例,可以并行执行 JavaScript 代码,从而充分利用多核 CPU 的优势。这意味着,你可以将 CPU 密集型任务放到 Worker Threads 中执行,避免阻塞主线程,提升应用的整体性能和用户体验。

Worker Threads 的基本用法回顾

在开始同步机制之前,我们先快速回顾一下 Worker Threads 的基本用法。这部分内容假设你已经对 Worker Threads 有一定的了解,如果不太熟悉,可以先参考 Node.js 官方文档或者其他入门教程。

创建 Worker

// main.js (主线程)
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
if (isMainThread) {
// 主线程代码
const worker = new Worker('./worker.js', { // worker.js 是 worker 线程的文件路径
workerData: { message: 'Hello from main thread!' } // 传递给 worker 线程的数据
});
worker.on('message', (message) => {
console.log('Received message from worker:', message);
});
worker.on('exit', (code) => {
console.log(`Worker stopped with exit code ${code}`);
});
worker.on('error', (error) => {
console.error('Worker error:', error);
});
} else {
// worker 线程代码
parentPort.on('message', (message) => {
console.log('Received message from main thread:', message);
parentPort.postMessage('Hello from worker thread!');
});
console.log('Worker started with data:', workerData);
}

worker.js (worker 线程)

// worker.js
const { parentPort, workerData } = require('worker_threads');
// 模拟一个 CPU 密集型任务
function heavyTask(n) {
let result = 0;
for (let i = 0; i < n; i++) {
result += Math.sqrt(i);
}
return result;
}
const result = heavyTask(100000000);
parentPort.postMessage({ result });

在这个例子中,main.js 是主线程,worker.js 是 worker 线程。主线程创建了一个 worker 实例,并通过 workerData 传递数据给 worker 线程。Worker 线程执行一个 CPU 密集型任务,并通过 parentPort.postMessage() 将结果发送回主线程。主线程通过 worker.on('message') 监听来自 worker 线程的消息。

Worker Threads 的同步机制:EventEmitter 方案

Worker Threads 之间如何进行同步?最基本的方式是使用 parentPort.postMessage()worker.on('message') 来进行消息传递。但是,对于更复杂的场景,仅仅使用简单的消息传递可能不够灵活。EventEmitter 提供了一种更强大的同步机制。

核心概念:事件驱动

EventEmitter 是一种事件驱动的编程模式。在 Node.js 中,EventEmitter 是一个核心模块,几乎所有涉及到异步操作的模块都使用了 EventEmitter。其核心概念是:

  • 事件 (Event):发生的事情,例如数据到达、任务完成等。
  • 监听器 (Listener):对特定事件做出响应的函数。
  • 发射 (Emit):触发事件,通知所有监听器。

使用 EventEmitter 实现 Worker Threads 同步

我们可以使用 EventEmitter 来在主线程和 worker 线程之间建立更复杂的通信和同步机制。我们可以将 EventEmitter 实例传递给 worker 线程,或者在 worker 线程中创建 EventEmitter 实例,并通过消息传递机制将其暴露给主线程。

示例:使用主线程的 EventEmitter

// main.js
const { Worker, isMainThread, workerData } = require('worker_threads');
const EventEmitter = require('events');
class MyEventEmitter extends EventEmitter {}
const myEmitter = new MyEventEmitter();
if (isMainThread) {
const worker = new Worker('./worker.js', {
workerData: { emitter: myEmitter } // 将 EventEmitter 实例传递给 worker 线程
});
// 监听 worker 线程的事件
myEmitter.on('task-completed', (result) => {
console.log('Task completed with result:', result);
});
// 模拟触发一个任务
setTimeout(() => {
console.log('Triggering a task...');
worker.postMessage({ task: 'do-something', data: { value: 10 } });
}, 1000);
} else {
// worker.js
const { parentPort, workerData } = require('worker_threads');
const emitter = workerData.emitter; // 获取 EventEmitter 实例
parentPort.on('message', (message) => {
if (message.task === 'do-something') {
// 模拟一个耗时任务
setTimeout(() => {
const result = message.data.value * 2;
console.log('Worker: Task completed, emitting event...');
emitter.emit('task-completed', result); // 触发事件
}, 2000);
}
});
}

在这个例子中,我们在主线程中创建了一个 EventEmitter 实例 myEmitter,并将其传递给 worker 线程。worker 线程通过 workerData.emitter 获取这个实例,并使用 emitter.emit() 触发事件。主线程通过 myEmitter.on() 监听这个事件,并处理任务的结果。这种方式的好处是,主线程和 worker 线程之间可以通过事件进行更灵活的通信,例如发布和订阅事件,实现更复杂的协作模式。

示例:使用 worker 线程的 EventEmitter

// main.js
const { Worker, isMainThread } = require('worker_threads');
if (isMainThread) {
const worker = new Worker('./worker.js');
// 监听 worker 线程的消息,获取 EventEmitter 实例
worker.on('message', (message) => {
if (message.type === 'emitter') {
const emitter = message.emitter; // 获取 EventEmitter 实例
// 监听 worker 线程的事件
emitter.on('task-completed', (result) => {
console.log('Task completed with result:', result);
});
// 模拟触发一个任务
setTimeout(() => {
console.log('Triggering a task...');
worker.postMessage({ task: 'do-something', data: { value: 10 } });
}, 1000);
}
});
worker.on('exit', (code) => {
console.log(`Worker stopped with exit code ${code}`);
});
worker.on('error', (error) => {
console.error('Worker error:', error);
});
} else {
// worker.js
const { parentPort } = require('worker_threads');
const EventEmitter = require('events');
class MyEventEmitter extends EventEmitter {}
const myEmitter = new MyEventEmitter();
// 将 EventEmitter 实例传递给主线程
parentPort.postMessage({ type: 'emitter', emitter: myEmitter });
parentPort.on('message', (message) => {
if (message.task === 'do-something') {
// 模拟一个耗时任务
setTimeout(() => {
const result = message.data.value * 2;
console.log('Worker: Task completed, emitting event...');
myEmitter.emit('task-completed', result); // 触发事件
}, 2000);
}
});
}

在这个例子中,worker 线程创建了一个 EventEmitter 实例,并通过消息传递将其发送给主线程。主线程通过监听 worker 线程的消息,获取这个 EventEmitter 实例,并监听 worker 线程触发的事件。这种方式的灵活性更高,worker 线程可以完全控制事件的触发和处理逻辑。

EventEmitter 方案的优缺点

  • 优点
    • 灵活性高:可以实现复杂的事件驱动的通信模式,例如发布/订阅模式。
    • 解耦性好:主线程和 worker 线程之间的依赖关系降低。
    • 可扩展性强:可以方便地添加新的事件和监听器。
  • 缺点
    • 复杂性增加:相对于简单的消息传递,需要处理更多的逻辑。
    • 序列化问题:传递 EventEmitter 实例需要进行序列化和反序列化,可能存在性能开销。需要注意,直接传递EventEmitter实例可能无法正常工作,因为它们通常包含一些非可序列化的内部状态。需要自定义序列化和反序列化逻辑,或者传递事件名称和数据。
    • 错误处理:需要仔细处理事件监听器中的错误,避免影响整个应用的稳定性。

Worker Threads 的同步机制:自定义事件方案

除了使用 EventEmitter,你还可以自定义事件来实现 Worker Threads 之间的同步。这种方案的灵活性更高,可以根据实际需求定义事件的格式和处理逻辑。

核心概念:事件定义

自定义事件方案的核心是定义事件。你需要定义事件的名称、数据格式和处理逻辑。事件的定义需要考虑以下几个方面:

  • 事件名称:用于标识事件的唯一字符串。
  • 事件数据:事件携带的数据,可以是任何 JavaScript 数据类型,例如字符串、数字、对象等。
  • 事件处理函数:用于处理事件的函数,接收事件数据作为参数。

使用自定义事件实现 Worker Threads 同步

示例:自定义事件方案

// main.js
const { Worker, isMainThread } = require('worker_threads');
if (isMainThread) {
const worker = new Worker('./worker.js');
// 监听 worker 线程的消息
worker.on('message', (message) => {
if (message.type === 'event') {
const { eventName, data } = message;
switch (eventName) {
case 'task-completed':
console.log('Task completed with result:', data);
break;
// 可以添加更多的事件处理逻辑
default:
console.log('Unknown event:', eventName);
}
}
});
// 模拟触发一个任务
setTimeout(() => {
console.log('Triggering a task...');
worker.postMessage({ task: 'do-something', data: { value: 10 } });
}, 1000);
worker.on('exit', (code) => {
console.log(`Worker stopped with exit code ${code}`);
});
worker.on('error', (error) => {
console.error('Worker error:', error);
});
} else {
// worker.js
const { parentPort } = require('worker_threads');
parentPort.on('message', (message) => {
if (message.task === 'do-something') {
// 模拟一个耗时任务
setTimeout(() => {
const result = message.data.value * 2;
console.log('Worker: Task completed, emitting event...');
// 发送自定义事件
parentPort.postMessage({ type: 'event', eventName: 'task-completed', data: result });
}, 2000);
}
});
}

在这个例子中,我们定义了一个名为 task-completed 的事件。当 worker 线程完成一个任务时,会发送一个包含事件名称和数据的消息给主线程。主线程通过监听 worker 线程的消息,并根据事件名称进行相应的处理。

自定义事件方案的优缺点

  • 优点
    • 灵活性高:可以根据实际需求定义事件的格式和处理逻辑。
    • 控制力强:可以更精确地控制事件的触发和处理流程。
    • 性能好:避免了 EventEmitter 的序列化开销。
  • 缺点
    • 代码量增加:需要手动定义事件和处理逻辑。
    • 维护成本高:需要维护事件名称和数据格式的一致性。
    • 错误处理:需要仔细处理消息传递中的错误。

其他同步机制和高级技巧

除了 EventEmitter 和自定义事件,还有一些其他的同步机制和高级技巧,可以帮助你更好地利用 Worker Threads。

1. 共享内存 (SharedArrayBuffer)

SharedArrayBuffer 允许你在不同的线程之间共享内存。这意味着,你可以直接在内存中读写数据,而无需通过消息传递。这可以大大提高性能,尤其是在处理大量数据时。

示例:使用 SharedArrayBuffer

// main.js
const { Worker, isMainThread, SharedArrayBuffer } = require('worker_threads');
if (isMainThread) {
const sharedBuffer = new SharedArrayBuffer(4); // 创建一个 4 字节的共享缓冲区
const int32Array = new Int32Array(sharedBuffer); // 创建一个 Int32Array 视图
int32Array[0] = 0; // 初始化数据
const worker = new Worker('./worker.js', {
workerData: { buffer: sharedBuffer }
});
worker.on('message', (message) => {
if (message === 'ready') {
// 模拟一个耗时任务
setTimeout(() => {
console.log('Incrementing value...');
int32Array[0]++; // 修改共享缓冲区的值
}, 1000);
}
});
worker.on('exit', (code) => {
console.log(`Worker stopped with exit code ${code}`);
});
worker.on('error', (error) => {
console.error('Worker error:', error);
});
console.log('Waiting for worker to be ready...');
}
// worker.js
const { parentPort, workerData } = require('worker_threads');
const int32Array = new Int32Array(workerData.buffer);
// 等待主线程准备好
setTimeout(() => {
console.log('Worker is ready.');
parentPort.postMessage('ready');
// 循环读取共享缓冲区的值
setInterval(() => {
console.log('Value in shared buffer:', int32Array[0]);
}, 500);
}, 500); // 模拟 worker 线程的初始化过程

在这个例子中,我们创建了一个 SharedArrayBuffer,并在主线程和 worker 线程之间共享。worker 线程可以读取和修改 SharedArrayBuffer 中的值,而主线程也可以读取和修改。需要注意的是,在使用 SharedArrayBuffer 时,需要使用原子操作 (Atomic operations) 来保证线程安全,例如 Atomics.load()Atomics.store()Atomics.compareExchange() 等。因为多个线程同时修改同一块内存区域时,可能会发生数据竞争的问题。

2. 锁 (Lock)

当多个线程需要访问共享资源时,为了避免数据竞争,可以使用锁来保证线程安全。Node.js 本身没有提供内置的锁机制,你需要自己实现或者使用第三方库。

示例:使用第三方锁库 (例如 async-mutex)

// main.js
const { Worker, isMainThread } = require('worker_threads');
const { Mutex } = require('async-mutex');
if (isMainThread) {
const mutex = new Mutex(); // 创建一个互斥锁
const worker1 = new Worker('./worker.js', {
workerData: { mutex, workerId: 1 }
});
const worker2 = new Worker('./worker.js', {
workerData: { mutex, workerId: 2 }
});
worker1.on('message', (message) => {
console.log(`Worker 1: ${message}`);
});
worker2.on('message', (message) => {
console.log(`Worker 2: ${message}`);
});
worker1.on('exit', (code) => {
console.log(`Worker 1 stopped with exit code ${code}`);
});
worker2.on('exit', (code) => {
console.log(`Worker 2 stopped with exit code ${code}`);
});
worker1.on('error', (error) => {
console.error('Worker 1 error:', error);
});
worker2.on('error', (error) => {
console.error('Worker 2 error:', error);
});
} else {
// worker.js
const { parentPort, workerData } = require('worker_threads');
const { mutex, workerId } = workerData;
async function performTask() {
const release = await mutex.acquire(); // 获取锁
try {
console.log(`Worker ${workerId}: Acquired lock, starting task...`);
// 模拟一个耗时任务
await new Promise(resolve => setTimeout(resolve, 2000));
console.log(`Worker ${workerId}: Task completed.`);
parentPort.postMessage('Task completed');
} finally {
release(); // 释放锁
console.log(`Worker ${workerId}: Released lock.`);
}
}
performTask();
}

在这个例子中,我们使用 async-mutex 库创建了一个互斥锁。每个 worker 线程在执行任务之前,需要先获取锁。当一个 worker 线程获取锁后,其他 worker 线程需要等待锁被释放才能继续执行。这样可以保证共享资源在同一时刻只能被一个线程访问,避免数据竞争。

3. 消息队列 (Message Queue)

消息队列是一种异步通信机制,可以用于在不同的线程之间传递消息。消息队列可以解耦不同的线程,提高系统的可扩展性和可靠性。Node.js 本身没有提供内置的消息队列,你需要使用第三方库或者自己实现。

示例:使用第三方消息队列库 (例如 bull)

// main.js
const { Worker, isMainThread } = require('worker_threads');
const Queue = require('bull');
if (isMainThread) {
const myQueue = new Queue('myQueue'); // 创建一个消息队列
const worker = new Worker('./worker.js', {
workerData: { queue: myQueue }
});
// 添加任务到消息队列
myQueue.add({ text: 'Hello, world!' });
worker.on('exit', (code) => {
console.log(`Worker stopped with exit code ${code}`);
});
worker.on('error', (error) => {
console.error('Worker error:', error);
});
} else {
// worker.js
const { parentPort, workerData } = require('worker_threads');
const { queue } = workerData;
// 监听消息队列中的任务
queue.process(async (job) => {
console.log(`Worker: Processing job with data: ${job.data.text}`);
// 模拟一个耗时任务
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('Worker: Job completed.');
return { result: 'Job completed successfully!' }; // 返回结果
});
// 监听队列的状态变化
queue.on('completed', (job, result) => {
console.log(`Job ${job.id} completed with result: ${result.result}`);
});
queue.on('failed', (job, err) => {
console.error(`Job ${job.id} failed with error: ${err}`);
});
}

在这个例子中,我们使用 bull 库创建了一个消息队列。主线程将任务添加到消息队列中,worker 线程从消息队列中获取任务并执行。当任务完成后,worker 线程会返回结果。消息队列可以用于处理异步任务,提高系统的吞吐量和响应速度。

4. 信号量 (Semaphore)

信号量是一种用于控制对共享资源的访问的同步原语。它维护一个计数器,表示可用资源的数量。线程在访问共享资源之前,需要先获取信号量。如果信号量的计数器为 0,则线程需要等待,直到有资源可用。Node.js 本身没有提供内置的信号量机制,你需要自己实现或者使用第三方库。

5. 避免死锁 (Deadlock)

在使用锁或其他同步机制时,需要特别注意避免死锁。死锁是指两个或多个线程互相等待对方释放资源,导致程序无法继续执行的状态。避免死锁的方法包括:

  • 避免循环依赖:确保线程获取资源的顺序一致。
  • 设置超时时间:在获取锁时设置超时时间,避免线程无限期地等待。
  • 使用锁的层次结构:将资源按照层次结构进行排序,确保线程按照固定的顺序获取锁。

实际项目中的应用场景

了解了 Worker Threads 的同步机制之后,我们来看看在实际项目中如何应用它们。

1. 并行处理大规模数据

当需要处理大规模数据时,可以使用 Worker Threads 将数据分成多个部分,并在不同的线程中并行处理。例如,在图像处理、视频编码、数据分析等场景中,可以使用 Worker Threads 来提高处理速度。

示例:并行处理图像

// main.js
const { Worker, isMainThread, workerData } = require('worker_threads');
const fs = require('fs');
const path = require('path');
if (isMainThread) {
const imagePath = path.join(__dirname, 'image.jpg'); // 替换为你的图像文件路径
const numWorkers = 4; // 使用 4 个 worker 线程
// 读取图像数据
fs.readFile(imagePath, (err, imageData) => {
if (err) {
console.error('Error reading image:', err);
return;
}
const imageBuffer = new Uint8Array(imageData);
const imageSize = imageBuffer.length;
const chunkSize = Math.floor(imageSize / numWorkers);
// 创建 worker 线程并分配数据
for (let i = 0; i < numWorkers; i++) {
const start = i * chunkSize;
const end = (i === numWorkers - 1) ? imageSize : (i + 1) * chunkSize;
const workerData = { imageData: imageBuffer.slice(start, end), workerId: i };
const worker = new Worker('./imageWorker.js', {
workerData: workerData
});
worker.on('message', (message) => {
console.log(`Worker ${message.workerId} processed chunk.`);
// 处理 worker 线程返回的结果,例如合并图像数据
});
worker.on('exit', (code) => {
console.log(`Worker stopped with exit code ${code}`);
});
worker.on('error', (error) => {
console.error('Worker error:', error);
});
}
});
} else {
// imageWorker.js
const { parentPort, workerData } = require('worker_threads');
// 模拟图像处理,例如颜色转换、滤镜等
function processImageChunk(imageData) {
// 在这里实现图像处理逻辑,例如使用 canvas、sharp 等库
// 这里只是一个简单的示例
for (let i = 0; i < imageData.length; i++) {
imageData[i] = 255 - imageData[i]; // 简单的反色处理
}
return imageData;
}
const processedData = processImageChunk(workerData.imageData);
parentPort.postMessage({ workerId: workerData.workerId, processedData });
}

在这个例子中,我们将图像数据分成多个部分,并在不同的 worker 线程中进行处理。每个 worker 线程处理一部分数据,并返回处理结果。主线程将 worker 线程返回的结果合并,得到最终的图像。

2. 后台任务处理

可以使用 Worker Threads 来处理后台任务,例如发送邮件、生成报表、定时任务等。这样可以避免阻塞主线程,提高应用的响应速度。

示例:后台发送邮件

// main.js
const { Worker, isMainThread } = require('worker_threads');
if (isMainThread) {
// 创建 worker 线程
const worker = new Worker('./emailWorker.js');
// 发送邮件任务到 worker 线程
worker.postMessage({ type: 'send-email', to: 'recipient@example.com', subject: 'Hello', body: 'This is a test email.' });
worker.on('message', (message) => {
console.log('Received message from worker:', message);
});
worker.on('exit', (code) => {
console.log(`Worker stopped with exit code ${code}`);
});
worker.on('error', (error) => {
console.error('Worker error:', error);
});
} else {
// emailWorker.js
const { parentPort } = require('worker_threads');
const nodemailer = require('nodemailer'); // 使用 nodemailer 库发送邮件
// 创建一个 nodemailer transporter (替换为你的邮件服务配置)
const transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: 'your_email@gmail.com',
pass: 'your_password'
}
});
parentPort.on('message', (message) => {
if (message.type === 'send-email') {
const { to, subject, body } = message;
// 发送邮件
transporter.sendMail({
from: 'your_email@gmail.com',
to: to,
subject: subject,
text: body
}, (error, info) => {
if (error) {
console.error('Error sending email:', error);
parentPort.postMessage({ type: 'email-sent', success: false, error: error });
} else {
console.log('Email sent:', info.response);
parentPort.postMessage({ type: 'email-sent', success: true });
}
});
}
});
}

在这个例子中,我们将发送邮件的任务放到 worker 线程中执行。主线程将邮件信息发送给 worker 线程,worker 线程使用 nodemailer 库发送邮件,并将结果返回给主线程。

3. 构建高性能 Web 服务器

在构建高性能 Web 服务器时,可以使用 Worker Threads 来处理并发请求。例如,可以使用一个 worker 线程池来处理 HTTP 请求,避免阻塞主线程,提高服务器的吞吐量和响应速度。

4. 数据库操作

虽然 Node.js 的异步特性使得数据库操作本身不会阻塞主线程,但是对于复杂的数据库查询和处理,可以使用 Worker Threads 来分担计算压力。例如,在处理大量数据时,可以使用 Worker Threads 来进行数据转换、聚合等操作。

总结

Worker Threads 是 Node.js 中一个非常强大的特性,可以帮助你构建高性能、高并发的应用程序。通过深入理解 Worker Threads 的同步机制,你可以更好地利用多核 CPU 的优势,提升应用的性能和用户体验。

本文介绍了 EventEmitter 和自定义事件两种同步机制,并分析了它们的优缺点。同时,还介绍了 SharedArrayBuffer、锁、消息队列等高级技巧,以及在实际项目中的应用场景。希望这些内容能帮助你更好地掌握 Worker Threads,并在实际项目中灵活应用。

记住,选择合适的同步机制取决于你的实际需求。在设计系统时,需要仔细考虑不同方案的优缺点,并选择最适合的方案。在实际项目中,可以根据需求灵活组合不同的同步机制,例如,可以使用 EventEmitter 来实现事件驱动的通信,使用 SharedArrayBuffer 来共享大量数据,使用锁来保护共享资源,使用消息队列来处理异步任务。在实践中不断学习和探索,你一定能成为 Worker Threads 的高手!

祝你在 Node.js 的世界里越走越远!

扩展阅读

希望这些资料能帮助你更深入地学习 Worker Threads。加油!

老码农的秘密花园 Node.jsWorker Threads多线程异步编程

评论点评

打赏赞助
sponsor

感谢您的支持让我们更好的前行

分享

QRcode

https://www.webkt.com/article/7932