笔者在 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 操作。构造函数的第一个参数是子线程执行的入口脚本程序,第二个参数包含一些配置项,可以指定一些初始参数。
详细文档见:
Node.js v11.4.0 Documentationnodejs.org
在使用 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 标准的关系暂不展开。
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
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 稳定后也可以开始进行迭代更新。