基本使用
对于cpu密集型可以通过worker_threads开启多线程优化,但是如果频繁创建和销毁也会很耗性能,所以官网说应该用线程池来优化下。
上面的示例为每个
parseJSAsync()
调用衍生工作线程。在实践中,为这些类型的任务使用工作线程池。否则,创建工作线程的开销可能会超过其收益。
The above example spawns a Worker thread for eachparseJSAsync()
call. In practice, use a pool of Workers for these kinds of tasks. Otherwise, the overhead of creating Workers would likely exceed their benefit.
worker_threads 工作线程 | Node.js v20 文档https://nodejs.cn/api/worker_threads.html
主线程
在主线程,worker_threads中引入isMainThread来判断是否为主线程。如果是主线程,我们就可以new Worker(src,{workerData})创建线程实例,给workerData属性赋值来传递数据,这样每个子线程都能通过workerData都能获取数据。
调用线程实例的postMessage,会触发子线程的message事件,开始执行子线程的任务。
我们监听线程实例的message和error方法,当子线程调用postMessage时(比如安排的任务完成)会触发主线程的message事件。
const express = require('express')
const { isMainThread, threadId, Worker } = require('worker_threads')
const app = express()
const port = 3232
app.get('/worker', (req, res) => {
// 通过isMainThread判断是主线程还是工作线程
if (isMainThread) {
const worker = new Worker('./worker.js', { workerData: { data: '这是workerData传递的数据' } })
// 向 Worker 线程发送数据//对象、字符串等都可以
worker.postMessage({ data: '这是postMessage传递的数据' }) //workerData和postMessage传递的数据都会深拷贝
// 监听来自 Worker 线程的消息
worker.on('message', (message) => {
console.log('从 Worker 接收到的消息:', message)
res.send('message')
setTimeout(() => {
// 等会就关闭线程
worker.terminate()
}, 2000)
})
worker.on('error', (error) => {
console.error('Worker error:', error)
res.status(500).send('Internal Server Error')
})
worker.on('exit', (code) => {
if (code !== 0) {
console.error(`Worker stopped with exit code ${code}`)
}
})
}
})
app.listen(port, () => {
console.log(`Express app listening at http://localhost:${port}`)
})
子线程
子线程能从worker_threads中拿到workerData,就是new Worker时传入的数据。
如果子线程的任务处理完成,可以通过parentPort.postMessage(data),给主线程发消息。
在子线程中引入的模块(比如下面的./store.js),会与主线程独立。
const { parentPort, workerData } = require('worker_threads')
const { arr } = require('./store.js')
// 监听来自主线程的消息
parentPort.on('message', (data) => {
console.log('从主线程接收到的数据:', data)
// 处理数据...
arr.push(Date.now().toString().slice(-6))
console.log(JSON.stringify(arr))
console.log(workerData)
// 向主线程发送消息
setTimeout(() => {
parentPort.postMessage({ result: '处理完成的数据' })
}, 2000)
})
parentPort.on('error', (error) => {
console.error(`Worker error: ${error}`)
})
parentPort.on('exit', (code) => {
console.log(`Worker exited with code ${code}`)
})
子线程引入的文件
子线程引入了./store.js,在这个文件中能从worker_threads中拿到workerData。如果这个文件也引入其他文件,那其他文件同样与主线程独立,并且也能拿到workerData。
const { workerData, threadId } = require('worker_threads')
console.log(JSON.stringify(workerData), threadId, '开启了子线程')
module.exports = {
arr: [1, 2, 3]
}
数据隔离
在worker_threads中通过“深拷贝”实现了一定的变量隔离,有深拷贝的数据有new Worker是传入的workerData,以及postMessage时传递的消息等。
因为每个子线程有自己的v8堆和调用栈,所以他的全局变量(global)和内存空间都是独立于主线程的。
内存共享-SharedArrayBuffer
SharedArrayBuffer是一种特殊的 ArrayBuffer,它允许在不同的 Worker 线程(包括主线程和 worker_threads 中的 Worker 线程)之间共享内存数据。但是多个线程同时使用时要注意同步问题,避免竞态条件(如果程序运行顺序的改变会影响最终结果,这就是一个竞态条件。哈哈),并且曾经因为安全漏洞被多个平台禁用(哦。),现在可以用了。
知道下有这样一个东西可以在线程之间共享内存。