[worker_threads]Node.js工作线程

注意: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.isMainThread

如果此代码未在Worker线程内运行,则为true。

worker.moveMessagePortToContext(port, contextifiedSandbox)

将MessagePort传输到其他vm上下文。 原始端口对象将变得不可用,并且返回的MessagePort实例将取代其位置。

返回的MessagePort将是目标上下文中的对象,并将从其全局Object类继承。 传递给port.onmessage()侦听器的对象也将在目标上下文中创建,并从其全局Object类继承。

但是,创建的MessagePort将不再继承自EventEmitter,只能使用port.onmessage()接收事件。

worker.parentPort

如果此线程是作为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);
  });
}

worker.receiveMessageOnPort(port)

从给定的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.SHARE_ENV

可以作为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.threadId使用。 对于单个进程内的每个Worker实例,此值都是唯一的。

worker.workerData

一个任意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!'.
}

MessageChannel 类

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);
}
}

MessagePort 类

worker.MessagePort类的实例表示异步双向通信通道的一端。 它可用于在不同的Worker之间传输结构化数据,内存区域和其他MessagePort。

除了MessagePorts是EventEmitters而不是EventTargets之外,此实现与浏览器MessagePorts匹配。

‘close’ 事件

一旦通道的任一侧断开连接,就会发出’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’ 事件

对于任何传入消息,都会发出’message’事件,其中包含port.postMessage()的克隆输入。

此事件的侦听器将接收传递给postMessage()的value参数的克隆,并且不包含其他参数。

port.close()

禁止在连接的任何一侧进一步发送消息。 当此MessagePort上没有进一步的通信时,可以调用此方法。

在通道的两个MessagePort实例上都将发出’close’事件。

port.postMessage(value[, transferList])

将JavaScript值发送到此通道的接收端。 值将以与HTML structured clone algorithm兼容的方式进行传输。

特别是,与JSON的显着区别是:

  • 值可能包含循环引用。
  • 值可能包含内置JS类型的实例,例如RegExps,BigInts,Maps,Sets等。
  • 值可能包含使用ArrayBuffers和SharedArrayBuffers的类型化数组。
  • 值可能包含WebAssembly.Module实例。
  • 值可能不包含MessagePorts以外的本机(C ++支持)对象。

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。

port.ref()

与unref()相反。 如果它是剩下的唯一活动句柄(默认行为),则在以前未引用()的端口上调用ref()将不会使程序退出。 如果端口是ref(),再次调用ref()将无效。

如果使用.on(‘message’)附加或删除了侦听器,则将根据事件是否存在侦听器自动对端口进行ref()和unref()。

port.start()

开始在此MessagePort上接收消息。 当将此端口用作事件发射器时,一旦连接了“消息”侦听器,它将自动被调用。

存在与Web MessagePort API奇偶校验的此方法。 在Node.js中,仅当不存在事件侦听器时,它才可用于忽略消息。 Node.js在处理.onmessage方面也有所不同。 设置它会自动调用.start(),但取消设置它会使消息排队,直到设置了新的处理程序或端口被丢弃为止。

port.unref()

如果这是事件系统中唯一的活动句柄,则在端口上调用unref()将允许线程退出。 如果端口已经被unref()调用,则再次调用unref()将无效。

如果使用.on(‘message’)附加或删除了侦听器,则将根据事件是否存在侦听器自动对端口进行ref()和unref()。

Worker 类

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();
  });
}

new Worker(filename[, options])

filename worker主文件的路径。 必须是以./或…/开头的绝对路径或相对路径(即相对于当前工作目录的相对路径)。 如果options.eval为true,则这是一个包含JavaScript代码而不是路径的字符串。

options

  • env 如果设置,则在Worker线程内指定process.env的初始值。作为一个特殊值,worker.SHARE_ENV可以用于指定父线程和子线程应该共享它们的环境变量。在这种情况下,对一个线程的process.env对象的更改也会影响另一个线程。默认值:process.env。
  • eval 如果为true,则将构造函数的第一个参数解释为工作线程联机后执行的脚本。
  • execArgv <string []>传递给工作程序的节点CLI选项的列表。不支持V8选项(例如–max-old-space-size)和影响进程的选项(例如–title)。如果设置,它将作为worker内部的process.execArgv提供。默认情况下,选项将从父线程继承。
  • stdin 如果将其设置为true,则worker.stdin将提供可写流,其内容将在worker中显示为process.stdin。默认情况下,不提供任何数据。
  • stdout 如果将其设置为true,则worker.stdout将不会自动通过管道传递到父级中的process.stdout。
  • stderr 如果将其设置为true,则worker.stderr将不会自动通过管道传递给父级中的process.stderr。
  • workerData 将被克隆并作为require(‘worker_threads’).workerData提供的任何JavaScript值。克隆将按照HTML structured clone algorithm中的描述进行,如果无法克隆对象(例如,因为它包含函数),则会引发错误。

‘error’ 事件

如果辅助线程抛出未捕获的异常,则发出’error’事件。 在这种情况下,worker被终止。

‘exit’ 事件

一旦worker停止工作,就会发出’exit’事件。 如果工作人员通过调用process.exit()退出,则exitCode参数将是传递的退出代码。

如果worker已终止,则exitCode参数将为1。

‘message’ 事件

当工作线程调用了require(‘worker_threads’).parentPort.postMessage()时,将发出’message’事件。 有关更多详细信息,请参见port.on(‘message’)事件。

‘online’ 事件

当工作线程开始执行JavaScript代码时,将发出’online’事件。

worker.postMessage(value[, transferList])

给worker发消息,该消息将通过require(‘worker_threads’).parentPort.on(‘message’)接收。 有关更多详细信息,请参见port.postMessage()。

worker.ref()

与unref()相反,如果它是剩下的唯一活动句柄(默认行为),则在先前未进行ref()的工作程序上调用ref()不会让程序退出。 如果对worker进行了ref()处理,则再次调用ref()将无效。

worker.stderr

这是一个可读流,其中包含在工作线程内写入process.stderr的数据。 如果未将stderr:true传递给Worker构造函数,则数据将通过管道传递到父线程的process.stderr流。

worker.stdin

如果将stdin:true传递给Worker构造函数,则这是可写的流。 写入此流的数据将在工作线程中作为process.stdin提供。

worker.stdout

这是一个可读流,其中包含在工作线程内写入process.stdout的数据。 如果未将stdout:true传递给Worker构造函数,则数据将通过管道传递到父线程的process.stdout流。

worker.terminate()

尽快停止在工作线程中执行所有JavaScript。 'exit’事件发出时返回一个用退出码作为成功参数的Promise。

worker.threadId

所引用线程的整数标识符。 在工作线程中,它可以作为require(‘worker_threads’).threadId使用。 对于单个进程内的每个Worker实例,此值都是唯一的。

worker.unref()

如果这是事件系统中唯一的活动句柄,则在工作线程上调用unref()将允许线程退出。 如果工作程序已经被unref()调用,则再次调用unref()将无效。

ref & unref

个人理解:调用了ref后,worker不会退出,只能等unref后才能退出。如果没有ref,unref没有用。相当于引用计数,ref计数加一,unref计数减一,最小为0,为0才可以退出。

疑问:ref后,worker出错、或者上调用了worker.terminate、或者在worker上调用了process.exit能否退出。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值