基本信息
笔者在 Node.js 最新的开发版本 v11.4.0 上测试该特性,目前需要添加 flag 才能引入 Worker Threads
,例如:
node --experimental-worker index.js
复制代码
Worker Threads
特性是在2018年6月20日的 v10.5.0 版本引入的:
node/CHANGELOG
目前该模块处于 Stability 1 - Experimental 阶段,改动会较大,不建议用于生产环境。
模块接口
const {
isMainThread, parentPort, workerData, threadId,
MessageChannel, MessagePort, Worker
} = require('worker_threads');
复制代码
该模块对象和类非常少,只有4个对象和3个类。
isMainThread
:false 表示当前为 worker 线程,false
表示为主线程parentPort
: 在 worker 线程里是表示父进程的MessagePort
类型的对象,在主线程里为null
workerData
: 在 worker 线程里是父进程创建 worker 线程时的初始化数据,在主线程里是undefined
threadId
: 在 worker 线程里是线程 ID,在父进程里是0
。MessageChannel
: 包含两个已经互相能够夸线程通信的MessagePort
类型对象,可用于创建自定义的通信频道,可参考样例二的实现。MessagePort
: 用于跨线程通信的句柄,继承了EventEmitter
,包括 close message 事件用于接收对象关闭和发送的消息,以及 close postMessage 等操作。Worker
: 主线程用于创建 worker 线程的对象类型,包含所有的 MessagePort 操作以及一些特有的子线程 meta data 操作。构造函数的第一个参数是子线程执行的入口脚本程序,第二个参数包含一些配置项,可以指定一些初始参数。 详细内容见文档
内存模型的变更
在使用 cluster
和 child_process
时通常使用 SharedArrayBuffer
来实现需要多进程共享的内存。
port.postMessage(value[, transferList])
复制代码
现在 Worker Threads 模块在 API 层不建议多线程共享内存,第一个参数 value 的值会被 clone 一份在接受消息的线程。transferList 只能传递 ArrayBuffer
或者 MessagePort
对象,传递 ArrayBuffer
会修改该 Buffer 的访问权限给接受消息的线程,传递 MessagePort
可以参考样例二。
所有跨线程消息的通信都通过走底层的 v8 序列化实现,更具体的 Worker Threads 和 v8 多线程模型以及和浏览器的 Web Worker 标准的关系暂不展开。
样例一:主线程和 worker 线程通信计数,计数到 5 后 worker 线程自杀
const {
isMainThread, parentPort, workerData, threadId,
MessageChannel, MessagePort, Worker
} = require('worker_threads');
function mainThread() {
const worker = new Worker(__filename, { workerData: 0 });
worker.on('exit', code => { console.log(`main: worker stopped with exit code ${code}`); });
worker.on('message', msg => {
console.log(`main: receive ${msg}`);
worker.postMessage(msg + 1);
});
}
function workerThread() {
console.log(`worker: threadId ${threadId} start with ${__filename}`);
console.log(`worker: workerDate ${workerData}`);
parentPort.on('message', msg => {
console.log(`worker: receive ${msg}`);
if (msg === 5) { process.exit(); }
parentPort.postMessage(msg);
}),
parentPort.postMessage(workerData);
}
if (isMainThread) {
mainThread();
} else {
workerThread();
}
复制代码
输出结果:
worker: threadId 1 start with /Users/azard/test/index.js
worker: workerDate 0
main: receive 0
worker: receive 1
main: receive 1
worker: receive 2
main: receive 2
worker: receive 3
main: receive 3
worker: receive 4
main: receive 4
worker: receive 5
main: receive 5
main: worker stopped with exit code 0
复制代码
样例二:使用 MessageChannel 让两个子线程直接通信
const {
isMainThread, parentPort, workerData, threadId,
MessageChannel, MessagePort, Worker
} = require('worker_threads');
if (isMainThread) {
const worker1 = new Worker(__filename);
const worker2 = new Worker(__filename);
const subChannel = new MessageChannel();
worker1.postMessage({ hereIsYourPort: subChannel.port1 }, [subChannel.port1]);
worker2.postMessage({ hereIsYourPort: subChannel.port2 }, [subChannel.port2]);
} else {
parentPort.once('message', (value) => {
value.hereIsYourPort.postMessage('hello');
value.hereIsYourPort.on('message', msg => {
console.log(`thread ${threadId}: receive ${msg}`);
});
});
}
复制代码
输出:
thread 2: receive hello
thread 1: receive hello
复制代码
总结
现在 Node.js 有了真多线程,不需要再用 cluster 或者 child_process 的多进程来处理一些问题了,相关的框架、库的模型在 Worker Threads 稳定后也可以开始进行迭代更新。