注意:Node.js 12之后,worker_threads才变成正式特性,不再需要通过–experimental-worker开启
worker_threads模块允许使用并行执行JavaScript的线程。 要使用它:
const worker = require('worker_threads');
工作者(线程)对于执行CPU密集型JavaScript操作非常有用。 他们在I/O密集型工作中帮助不大。 Node.js的内置异步I/O操作比Workers效率更高。
与child_process或cluster不同,worker_threads可以共享内存。 它们通过传输ArrayBuffer实例或共享SharedArrayBuffer实例来实现。
const {
Worker, isMainThread, parentPort, workerData
} = require('worker_threads');
if (isMainThread) {
module.exports = function parseJSAsync(script) {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, {
workerData: script
});
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0)
reject(new Error(`Worker stopped with exit code ${
code}`));
});
});
};
} else {
const {
parse } = require('some-js-parsing-library');
const script = workerData;
parentPort.postMessage(parse(script));
}
上面的示例为每个parse()调用生成一个Worker线程。 在实际实践中,请使用线程池代替这种方式。 否则,创建Workers的开销可能会超出其收益。
应用线程池时,请使用AsyncResource API通知诊断工具(例如为了提供异步堆栈跟踪)有关任务及其结果之间的相关性。
如果此代码未在Worker线程内运行,则为true。
将MessagePort传输到其他vm上下文。 原始端口对象将变得不可用,并且返回的MessagePort实例将取代其位置。
返回的MessagePort将是目标上下文中的对象,并将从其全局Object类继承。 传递给port.onmessage()侦听器的对象也将在目标上下文中创建,并从其全局Object类继承。
但是,创建的MessagePort将不再继承自EventEmitter,只能使用port.onmessage()接收事件。
如果此线程是作为Worker产生的,它将是一个MessagePort,允许与父线程进行通信。 使用parentPort.postMessage()发送的消息将在使用worker.on(‘message’)的父线程中可用,使用parent.worker.postMessage()从父线程发送的消息将在此线程中使用parentPort.on(‘message’)。
const {
Worker, isMainThread, parentPort } = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
worker.once('message', (message) => {
console.log(message); // Prints 'Hello, world!'.
});
worker.postMessage('Hello, world!');
} else {
// When a message from the parent thread is received, send it back:
parentPort.once('message', (message) => {
parentPort.postMessage(message);
});
}
从给定的MessagePort接收一条消息。 如果没有可用的消息,则返回undefined,否则返回一个具有单个message属性的对象,该属性包含消息有效负载,对应于MessagePort队列中最旧的消息。
const {
MessageChannel, receiveMessageOnPort } = require('worker_threads');
const {
port1, port2 } = new MessageChannel();
port1.postMessage({
hello: 'world' });
console.log(receiveMessageOnPort(port2));
// Prints: { message: { hello: 'world' } }
console.log(receiveMessageOnPort(port2));
// Prints: undefined
使用此功能时,将不会发出“ message”事件,并且不会调用onmessage侦听器。
可以作为Worker构造函数的env选项传递的特殊值,以指示当前线程和Worker线程应共享对同一组环境变量的读写访问权限。
const {
Worker, SHARE_ENV } = require('worker_threads');
new Worker('process.env.SET_IN_WORKER = "foo"', {
eval: true, env: SHARE_ENV })
.on('exit', () => {
console.log(process.env.SET_IN_WORKER); // Prints 'foo'.
});
当前线程的整数标识符。 在相应的工作程序对象(如果有)上,它可以作为worker.threadId使用。 对于单个进程内的每个Worker实例,此值都是唯一的。
一个任意JavaScript值,其中包含传递给该线程的Worker构造函数的数据的克隆。
和使用postMessage()一样,数据会被克隆,遵循HTML structured clone algorithm。
const {
Worker, isMainThread, workerData } = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename, {
workerData: 'Hello, world!' });
} else {
console.log(workerData); // Prints 'Hello, world!'.
}
worker.MessageChannel类的实例表示一个异步的双向通信通道。
MessageChannel没有自己的方法。 新的MessageChannel()产生一个具有port1和port2属性的对象,该属性引用链接的MessagePort实例。
const {
MessageChannel } = require('worker_threads');
const {
port1, port2 } = new MessageChannel();
port1.on('message', (message) => console.log('received', message));
port2.postMessage({
foo: 'bar' });
// Prints: received { foo: 'bar' } from the `port1.on('message')` listener
多个worker之间可以利用MessageChannel传递数据
参考:MessageChannel是什么,怎么使用?
// w1拿了port1,w2拿了port2,然后w1和w2就可以自己通信了
var w1 = new Worker("worker1.js");
var w2 = new Worker("worker2.js");
var ch = new MessageChannel();
w1.postMessage("port1", [ch.port1]);
w2.postMessage("port2", [ch.port2]);
w2.onmessage = function(e) {
console.log(e.data);
}
}
worker.MessagePort类的实例表示异步双向通信通道的一端。 它可用于在不同的Worker之间传输结构化数据,内存区域和其他MessagePort。
除了MessagePorts是EventEmitters而不是EventTargets之外,此实现与浏览器MessagePorts匹配。
一旦通道的任一侧断开连接,就会发出’close’事件。
const {
MessageChannel } = require('worker_threads');
const {
port1, port2 } = new MessageChannel();
// Prints:
// foobar
// closed!
port2.on('message', (message) => console.log(message));
port2.on('close', () => console.log('closed!'));
port1.postMessage('foobar');
port1.close();
对于任何传入消息,都会发出’message’事件,其中包含port.postMessage()的克隆输入。
此事件的侦听器将接收传递给postMessage()的value参数的克隆,并且不包含其他参数。
禁止在连接的任何一侧进一步发送消息。 当此MessagePort上没有进一步的通信时,可以调用此方法。
在通道的两个MessagePort实例上都将发出’close’事件。
将JavaScript值发送到此通道的接收端。 值将以与HTML structured clone algorithm兼容的方式进行传输。
特别是,与JSON的显着区别是:
const {
MessageChannel } = require('worker_threads');
const {
port1, port2 } = new MessageChannel();
port1.on('message', (message) => console.log(message));
const circularData = {
};
circularData.foo = circularData;
// Prints: { foo: [Circular] }
port2.postMessage(circularData);
transferList可以是ArrayBuffer和MessagePort对象的列表。 传输后,它们将不再在通道的发送端使用(即使它们不包含在值中)。
与子进程不同,当前不支持传输句柄(例如网络套接字)。
如果value包含SharedArrayBuffer实例,则可以从任一线程访问这些实例。 它们不能在transferList中列出。
值可能仍然包含不在transferList中的ArrayBuffer实例; 在这种情况下,底层内存将被复制而不是移动。
const {
MessageChannel } = require('worker_threads');
const {
port1, port2 } = new MessageChannel();
port1.on('message', (message) => console.log(message));
const uint8Array = new Uint8Array([ 1, 2, 3, 4 ]);
// This posts a copy of `uint8Array`:
port2.postMessage(uint8Array);
// This does not copy data, but renders `uint8Array` unusable:
port2.postMessage(uint8Array, [ uint8Array.buffer ]);
// The memory for the `sharedUint8Array` will be accessible from both the
// original and the copy received by `.on('message')`:
// 这里没说清楚,到底是复制过去的,还是共享引用了
const sharedUint8Array = new Uint8Array(new SharedArrayBuffer(4));
port2.postMessage(sharedUint8Array);
// This transfers a freshly created message port to the receiver.
// This can be used, for example, to create communication channels between
// multiple `Worker` threads that are children of the same parent thread.
const otherChannel = new MessageChannel();
port2.postMessage({
port: otherChannel.port1 }, [ otherChannel.port1 ]);
由于对象克隆使用结构化克隆算法,因此不会保留不可枚举的属性,属性访问器和对象原型。 特别是,在接收端,Buffer对象将作为普通的Uint8Arrays读取。
消息对象将被立即克隆,并且可以在发布后进行修改而不会产生副作用。
有关此API背后的序列化和反序列化机制的更多信息,请参见v8模块的序列化API。
与unref()相反。 如果它是剩下的唯一活动句柄(默认行为),则在以前未引用()的端口上调用ref()将不会使程序退出。 如果端口是ref(),再次调用ref()将无效。
如果使用.on(‘message’)附加或删除了侦听器,则将根据事件是否存在侦听器自动对端口进行ref()和unref()。
开始在此MessagePort上接收消息。 当将此端口用作事件发射器时,一旦连接了“消息”侦听器,它将自动被调用。
存在与Web MessagePort API奇偶校验的此方法。 在Node.js中,仅当不存在事件侦听器时,它才可用于忽略消息。 Node.js在处理.onmessage方面也有所不同。 设置它会自动调用.start(),但取消设置它会使消息排队,直到设置了新的处理程序或端口被丢弃为止。
如果这是事件系统中唯一的活动句柄,则在端口上调用unref()将允许线程退出。 如果端口已经被unref()调用,则再次调用unref()将无效。
如果使用.on(‘message’)附加或删除了侦听器,则将根据事件是否存在侦听器自动对端口进行ref()和unref()。
Worker类代表一个独立的JavaScript执行线程。 大多数Node.js API都在其中可用。
Worker环境中的显着差异是:
process.stdin,process.stdout和process.stderr可以被父线程重定向。
require(‘worker_threads’)。isMainThread属性设置为false。
require(‘worker_threads’)。parentPort消息端口可用。
process.exit()不会停止整个程序,仅停止单个线程,而process.abort()不可用。
无法使用process.chdir()和设置组或用户ID的处理方法。
除非另有说明,否则process.env是父线程的环境变量的副本。对一个副本的更改将在其他线程中不可见,并且对本机加载项也不可见(除非worker.SHARE_ENV作为env选项传递给Worker构造函数)。
process.title无法修改。
信号将不会通过process.on(’…’)传递。
由于worker.terminate()的执行,执行可能随时停止。
无法访问父进程的IPC通道。
不支持trace_events模块。
满足特定条件时,原生扩展才能在多线程中加载。
与Web Workers和cluster模块一样,可以通过线程间消息传递来实现双向通信。
在内部,一个Worker具有一对内置的MessagePort,在创建该Worker时它们已经相互关联。
虽然父端的MessagePort对象没有直接公开,但其功能是通过父线程的Worker对象上的worker.postMessage()和worker.on(‘message’)事件公开的。
要创建自定义消息传递通道(建议使用默认的全局channel,因为这样可以促进关注点的分离,因此建议用户使用该方法),用户可以在任一线程上创建一个MessageChannel对象,并将该MessageChannel上的MessagePort中的一个通过预先存在的channel传递给另一个线,例如一个全局channel。
有关如何传递消息以及可以通过线程屏障成功传输哪种JavaScript值的更多信息,请参见port.postMessage()。
const assert = require('assert');
const {
Worker, MessageChannel, MessagePort, isMainThread, parentPort
} = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
const subChannel = new MessageChannel();
worker.postMessage({
hereIsYourPort: subChannel.port1 }, [subChannel.port1]);
subChannel.port2.on('message', (value) => {
console.log('received:', value);
});
} else {
parentPort.once('message', (value) => {
assert(value.hereIsYourPort instanceof MessagePort);
value.hereIsYourPort.postMessage('the worker is sending this');
value.hereIsYourPort.close();
});
}
filename worker主文件的路径。 必须是以./或…/开头的绝对路径或相对路径(即相对于当前工作目录的相对路径)。 如果options.eval为true,则这是一个包含JavaScript代码而不是路径的字符串。
options
如果辅助线程抛出未捕获的异常,则发出’error’事件。 在这种情况下,worker被终止。
一旦worker停止工作,就会发出’exit’事件。 如果工作人员通过调用process.exit()退出,则exitCode参数将是传递的退出代码。
如果worker已终止,则exitCode参数将为1。
当工作线程调用了require(‘worker_threads’).parentPort.postMessage()时,将发出’message’事件。 有关更多详细信息,请参见port.on(‘message’)事件。
当工作线程开始执行JavaScript代码时,将发出’online’事件。
给worker发消息,该消息将通过require(‘worker_threads’).parentPort.on(‘message’)接收。 有关更多详细信息,请参见port.postMessage()。
与unref()相反,如果它是剩下的唯一活动句柄(默认行为),则在先前未进行ref()的工作程序上调用ref()不会让程序退出。 如果对worker进行了ref()处理,则再次调用ref()将无效。
这是一个可读流,其中包含在工作线程内写入process.stderr的数据。 如果未将stderr:true传递给Worker构造函数,则数据将通过管道传递到父线程的process.stderr流。
如果将stdin:true传递给Worker构造函数,则这是可写的流。 写入此流的数据将在工作线程中作为process.stdin提供。
这是一个可读流,其中包含在工作线程内写入process.stdout的数据。 如果未将stdout:true传递给Worker构造函数,则数据将通过管道传递到父线程的process.stdout流。
尽快停止在工作线程中执行所有JavaScript。 'exit’事件发出时返回一个用退出码作为成功参数的Promise。
所引用线程的整数标识符。 在工作线程中,它可以作为require(‘worker_threads’).threadId使用。 对于单个进程内的每个Worker实例,此值都是唯一的。
如果这是事件系统中唯一的活动句柄,则在工作线程上调用unref()将允许线程退出。 如果工作程序已经被unref()调用,则再次调用unref()将无效。
个人理解:调用了ref后,worker不会退出,只能等unref后才能退出。如果没有ref,unref没有用。相当于引用计数,ref计数加一,unref计数减一,最小为0,为0才可以退出。
疑问:ref后,worker出错、或者上调用了worker.terminate、或者在worker上调用了process.exit能否退出。