Hilos de trabajo
[Estable: 2 - Estable]
Estable: 2 Estabilidad: 2 - Estable
Código fuente: lib/worker_threads.js
El módulo node:worker_threads
permite el uso de hilos que ejecutan JavaScript en paralelo. Para acceder a él:
const worker = require('node:worker_threads')
Los workers (hilos) son útiles para realizar operaciones de JavaScript intensivas en CPU. No ayudan mucho con el trabajo intensivo en I/O. Las operaciones de E/S asíncronas integradas de Node.js son más eficientes de lo que pueden ser los Workers.
A diferencia de child_process
o cluster
, worker_threads
puede compartir memoria. Lo hacen transfiriendo instancias de ArrayBuffer
o compartiendo instancias de SharedArrayBuffer
.
const { Worker, isMainThread, parentPort, workerData } = require('node: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 se detuvo con código de salida ${code}`))
})
})
}
} else {
const { parse } = require('some-js-parsing-library')
const script = workerData
parentPort.postMessage(parse(script))
}
El ejemplo anterior genera un hilo Worker para cada llamada a parseJSAsync()
. En la práctica, utilice un grupo de Workers para este tipo de tareas. De lo contrario, la sobrecarga de crear Workers probablemente excedería su beneficio.
Al implementar un grupo de trabajadores, utilice la API AsyncResource
para informar a las herramientas de diagnóstico (por ejemplo, para proporcionar seguimientos de pila asíncronos) sobre la correlación entre las tareas y sus resultados. Consulte "Uso de AsyncResource
para un grupo de hilos Worker
" en la documentación de async_hooks
para ver un ejemplo de implementación.
Los hilos Worker heredan las opciones no específicas del proceso de forma predeterminada. Consulte Opciones del constructor Worker
para saber cómo personalizar las opciones del hilo Worker, específicamente las opciones argv
y execArgv
.
worker.getEnvironmentData(key)
[Historial]
Versión | Cambios |
---|---|
v17.5.0, v16.15.0 | Ya no es experimental. |
v15.12.0, v14.18.0 | Añadido en: v15.12.0, v14.18.0 |
key
<any> Cualquier valor JavaScript arbitrario y clonable que pueda usarse como clave de un <Map>.- Devuelve: <any>
Dentro de un hilo de worker, worker.getEnvironmentData()
devuelve un clon de los datos pasados a worker.setEnvironmentData()
del hilo de origen. Cada nuevo Worker
recibe su propia copia de los datos de entorno automáticamente.
const { Worker, isMainThread, setEnvironmentData, getEnvironmentData } = require('node:worker_threads')
if (isMainThread) {
setEnvironmentData('Hello', 'World!')
const worker = new Worker(__filename)
} else {
console.log(getEnvironmentData('Hello')) // Imprime 'World!'.
}
worker.isMainThread
Agregado en: v10.5.0
Es true
si este código no se está ejecutando dentro de un hilo Worker
.
const { Worker, isMainThread } = require('node:worker_threads')
if (isMainThread) {
// Esto vuelve a cargar el archivo actual dentro de una instancia de Worker.
new Worker(__filename)
} else {
console.log('¡Dentro del Worker!')
console.log(isMainThread) // Imprime 'false'.
}
worker.markAsUntransferable(object)
Agregado en: v14.5.0, v12.19.0
object
<any> Cualquier valor arbitrario de JavaScript.
Marca un objeto como no transferible. Si object
aparece en la lista de transferencia de una llamada a port.postMessage()
, se genera un error. Esta es una operación no operativa si object
es un valor primitivo.
En particular, esto tiene sentido para objetos que se pueden clonar, en lugar de transferir, y que son utilizados por otros objetos en el lado emisor. Por ejemplo, Node.js marca los ArrayBuffer
que utiliza para su Buffer
pool con esto.
Esta operación no se puede deshacer.
const { MessageChannel, markAsUntransferable } = require('node:worker_threads')
const pooledBuffer = new ArrayBuffer(8)
const typedArray1 = new Uint8Array(pooledBuffer)
const typedArray2 = new Float64Array(pooledBuffer)
markAsUntransferable(pooledBuffer)
const { port1 } = new MessageChannel()
try {
// Esto generará un error, porque pooledBuffer no es transferible.
port1.postMessage(typedArray1, [typedArray1.buffer])
} catch (error) {
// error.name === 'DataCloneError'
}
// La siguiente línea imprime el contenido de typedArray1 -- todavía posee
// su memoria y no se ha transferido. Sin
// `markAsUntransferable()`, esto imprimiría un Uint8Array vacío y la
// llamada a postMessage habría tenido éxito.
// typedArray2 también está intacto.
console.log(typedArray1)
console.log(typedArray2)
No existe un equivalente a esta API en los navegadores.
worker.isMarkedAsUntransferable(object)
Agregado en: v21.0.0
Verifica si un objeto está marcado como no transferible con markAsUntransferable()
.
const { markAsUntransferable, isMarkedAsUntransferable } = require('node:worker_threads')
const pooledBuffer = new ArrayBuffer(8)
markAsUntransferable(pooledBuffer)
isMarkedAsUntransferable(pooledBuffer) // Devuelve true.
No existe un equivalente a esta API en los navegadores.
worker.markAsUncloneable(object)
Agregado en: v23.0.0
object
<any> Cualquier valor arbitrario de JavaScript.
Marca un objeto como no clonable. Si object
se utiliza como message
en una llamada a port.postMessage()
, se genera un error. Esta es una operación sin efecto si object
es un valor primitivo.
Esto no tiene efecto en ArrayBuffer
ni en ningún objeto similar a Buffer
.
Esta operación no se puede deshacer.
const { markAsUncloneable } = require('node:worker_threads')
const anyObject = { foo: 'bar' }
markAsUncloneable(anyObject)
const { port1 } = new MessageChannel()
try {
// Esto generará un error porque anyObject no es clonable.
port1.postMessage(anyObject)
} catch (error) {
// error.name === 'DataCloneError'
}
No existe un equivalente a esta API en los navegadores.
worker.moveMessagePortToContext(port, contextifiedSandbox)
Añadido en: v11.13.0
port
<MessagePort> El puerto de mensajes a transferir.contextifiedSandbox
<Object> Un objeto contextualizado como el devuelto por el métodovm.createContext()
.- Devuelve: <MessagePort>
Transfiere un MessagePort
a un contexto vm
diferente. El objeto port
original se vuelve inutilizable, y la instancia MessagePort
devuelta toma su lugar.
El MessagePort
devuelto es un objeto en el contexto de destino y hereda de su clase global Object
. Los objetos pasados al listener port.onmessage()
también se crean en el contexto de destino y heredan de su clase global Object
.
Sin embargo, el MessagePort
creado ya no hereda de EventTarget
, y solo se puede usar port.onmessage()
para recibir eventos usando este.
worker.parentPort
Añadido en: v10.5.0
Si este hilo es un Worker
, este es un MessagePort
que permite la comunicación con el hilo padre. Los mensajes enviados usando parentPort.postMessage()
están disponibles en el hilo padre usando worker.on('message')
, y los mensajes enviados desde el hilo padre usando worker.postMessage()
están disponibles en este hilo usando parentPort.on('message')
.
const { Worker, isMainThread, parentPort } = require('node:worker_threads')
if (isMainThread) {
const worker = new Worker(__filename)
worker.once('message', message => {
console.log(message) // Imprime '¡Hola, mundo!'.
})
worker.postMessage('¡Hola, mundo!')
} else {
// Cuando se recibe un mensaje del hilo padre, se envía de vuelta:
parentPort.once('message', message => {
parentPort.postMessage(message)
})
}
worker.postMessageToThread(threadId, value[, transferList][, timeout])
Agregado en: v22.5.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1.1 - Desarrollo activo
threadId
<number> El ID del hilo de destino. Si el ID del hilo no es válido, se lanzará un errorERR_WORKER_MESSAGING_FAILED
. Si el ID del hilo de destino es el ID del hilo actual, se lanzará un errorERR_WORKER_MESSAGING_SAME_THREAD
.value
<any> El valor a enviar.transferList
<Object[]> Si uno o más objetos tipoMessagePort
se pasan envalue
, se requiere unatransferList
para esos elementos o se lanzaERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST
. Consulteport.postMessage()
para obtener más información.timeout
<number> Tiempo de espera para que el mensaje se entregue en milisegundos. Por defecto esundefined
, lo que significa esperar indefinidamente. Si la operación agota el tiempo, se lanzará un errorERR_WORKER_MESSAGING_TIMEOUT
.- Devuelve: <Promise> Una promesa que se cumple si el mensaje fue procesado con éxito por el hilo de destino.
Envía un valor a otro worker, identificado por su ID de hilo.
Si el hilo de destino no tiene un listener para el evento workerMessage
, entonces la operación lanzará un error ERR_WORKER_MESSAGING_FAILED
.
Si el hilo de destino lanzó un error mientras procesaba el evento workerMessage
, entonces la operación lanzará un error ERR_WORKER_MESSAGING_ERRORED
.
Este método debe usarse cuando el hilo de destino no es el padre o hijo directo del hilo actual. Si los dos hilos son padre-hijo, use require('node:worker_threads').parentPort.postMessage()
y worker.postMessage()
para que los hilos se comuniquen.
El ejemplo siguiente muestra el uso de postMessageToThread
: crea 10 hilos anidados, el último intentará comunicarse con el hilo principal.
import { fileURLToPath } from 'node:url'
import process from 'node:process'
import { postMessageToThread, threadId, workerData, Worker } from 'node:worker_threads'
const channel = new BroadcastChannel('sync')
const level = workerData?.level ?? 0
if (level < 10) {
const worker = new Worker(fileURLToPath(import.meta.url), {
workerData: { level: level + 1 },
})
}
if (level === 0) {
process.on('workerMessage', (value, source) => {
console.log(`${source} -> ${threadId}:`, value)
postMessageToThread(source, { message: 'pong' })
})
} else if (level === 10) {
process.on('workerMessage', (value, source) => {
console.log(`${source} -> ${threadId}:`, value)
channel.postMessage('done')
channel.close()
})
await postMessageToThread(0, { message: 'ping' })
}
channel.onmessage = channel.close
const { postMessageToThread, threadId, workerData, Worker } = require('node:worker_threads')
const channel = new BroadcastChannel('sync')
const level = workerData?.level ?? 0
if (level < 10) {
const worker = new Worker(__filename, {
workerData: { level: level + 1 },
})
}
if (level === 0) {
process.on('workerMessage', (value, source) => {
console.log(`${source} -> ${threadId}:`, value)
postMessageToThread(source, { message: 'pong' })
})
} else if (level === 10) {
process.on('workerMessage', (value, source) => {
console.log(`${source} -> ${threadId}:`, value)
channel.postMessage('done')
channel.close()
})
postMessageToThread(0, { message: 'ping' })
}
channel.onmessage = channel.close
worker.receiveMessageOnPort(port)
[Historial]
Versión | Cambios |
---|---|
v15.12.0 | El argumento port ahora también puede referirse a un BroadcastChannel . |
v12.3.0 | Añadido en: v12.3.0 |
port
<MessagePort> | <BroadcastChannel>- Devuelve: <Object> | <undefined>
Recibe un solo mensaje desde un MessagePort
dado. Si no hay ningún mensaje disponible, se devuelve undefined
, de lo contrario, se devuelve un objeto con una única propiedad message
que contiene la carga útil del mensaje, correspondiente al mensaje más antiguo en la cola del MessagePort
.
const { MessageChannel, receiveMessageOnPort } = require('node:worker_threads')
const { port1, port2 } = new MessageChannel()
port1.postMessage({ hello: 'world' })
console.log(receiveMessageOnPort(port2))
// Imprime: { message: { hello: 'world' } }
console.log(receiveMessageOnPort(port2))
// Imprime: undefined
Cuando se usa esta función, no se emite ningún evento 'message'
y no se invoca al listener onmessage
.
worker.resourceLimits
Añadido en: v13.2.0, v12.16.0
Proporciona el conjunto de restricciones de recursos del motor de JS dentro de este hilo Worker. Si la opción resourceLimits
se pasó al constructor Worker
, esto coincide con sus valores.
Si esto se usa en el hilo principal, su valor es un objeto vacío.
worker.SHARE_ENV
Añadido en: v11.14.0
Un valor especial que se puede pasar como la opción env
del constructor Worker
, para indicar que el hilo actual y el hilo Worker deben compartir acceso de lectura y escritura al mismo conjunto de variables de entorno.
const { Worker, SHARE_ENV } = require('node:worker_threads')
new Worker('process.env.SET_IN_WORKER = "foo"', { eval: true, env: SHARE_ENV }).on('exit', () => {
console.log(process.env.SET_IN_WORKER) // Imprime 'foo'.
})
worker.setEnvironmentData(key[, value])
[Historial]
Versión | Cambios |
---|---|
v17.5.0, v16.15.0 | Ya no es experimental. |
v15.12.0, v14.18.0 | Añadido en: v15.12.0, v14.18.0 |
key
<any> Cualquier valor JavaScript arbitrario y clonable que se pueda usar como clave de <Map>.value
<any> Cualquier valor JavaScript arbitrario y clonable que se clonará y se pasará automáticamente a todas las nuevas instancias deWorker
. Sivalue
se pasa comoundefined
, se eliminará cualquier valor establecido previamente para lakey
.
La API worker.setEnvironmentData()
establece el contenido de worker.getEnvironmentData()
en el hilo actual y en todas las nuevas instancias de Worker
generadas desde el contexto actual.
worker.threadId
Añadido en: v10.5.0
Un identificador entero para el hilo actual. En el objeto worker correspondiente (si existe), está disponible como worker.threadId
. Este valor es único para cada instancia de Worker
dentro de un único proceso.
worker.workerData
Agregado en: v10.5.0
Un valor de JavaScript arbitrario que contiene un clon de los datos pasados al constructor Worker
de este hilo.
Los datos se clonan como si se utilizara postMessage()
, según el algoritmo de clonación estructurada HTML.
const { Worker, isMainThread, workerData } = require('node:worker_threads')
if (isMainThread) {
const worker = new Worker(__filename, { workerData: '¡Hola, mundo!' })
} else {
console.log(workerData) // Imprime '¡Hola, mundo!'.
}
Clase: BroadcastChannel extends EventTarget
[Historial]
Versión | Cambios |
---|---|
v18.0.0 | Ya no es experimental. |
v15.4.0 | Agregado en: v15.4.0 |
Las instancias de BroadcastChannel
permiten la comunicación asíncrona de uno a muchos con todas las demás instancias de BroadcastChannel
vinculadas al mismo nombre de canal.
'use strict'
const { isMainThread, BroadcastChannel, Worker } = require('node:worker_threads')
const bc = new BroadcastChannel('hello')
if (isMainThread) {
let c = 0
bc.onmessage = event => {
console.log(event.data)
if (++c === 10) bc.close()
}
for (let n = 0; n < 10; n++) new Worker(__filename)
} else {
bc.postMessage('hola desde cada worker')
bc.close()
}
new BroadcastChannel(name)
Añadido en: v15.4.0
name
<any> El nombre del canal al que conectarse. Se permite cualquier valor de JavaScript que se pueda convertir en una cadena utilizando${name}
.
broadcastChannel.close()
Añadido en: v15.4.0
Cierra la conexión BroadcastChannel
.
broadcastChannel.onmessage
Añadido en: v15.4.0
- Tipo: <Function> Se invoca con un solo argumento
MessageEvent
cuando se recibe un mensaje.
broadcastChannel.onmessageerror
Añadido en: v15.4.0
- Tipo: <Function> Se invoca cuando un mensaje recibido no se puede deserializar.
broadcastChannel.postMessage(message)
Añadido en: v15.4.0
message
<any> Cualquier valor de JavaScript que se pueda clonar.
broadcastChannel.ref()
Agregado en: v15.4.0
Opuesto a unref()
. Llamar a ref()
en un BroadcastChannel previamente unref()
no permite que el programa se cierre si es el único controlador activo restante (el comportamiento predeterminado). Si el puerto es ref()
ed, llamar a ref()
nuevamente no tiene efecto.
broadcastChannel.unref()
Agregado en: v15.4.0
Llamar a unref()
en un BroadcastChannel permite que el hilo se cierre si este es el único controlador activo en el sistema de eventos. Si el BroadcastChannel ya está unref()
ed, llamar a unref()
nuevamente no tiene efecto.
Clase: MessageChannel
Agregado en: v10.5.0
Las instancias de la clase worker.MessageChannel
representan un canal de comunicaciones asíncrono y bidireccional. El MessageChannel
no tiene métodos propios. new MessageChannel()
produce un objeto con propiedades port1
y port2
, que hacen referencia a instancias de MessagePort
enlazadas.
const { MessageChannel } = require('node:worker_threads')
const { port1, port2 } = new MessageChannel()
port1.on('message', message => console.log('recibido', message))
port2.postMessage({ foo: 'bar' })
// Imprime: recibido { foo: 'bar' } desde el listener `port1.on('message')`
Clase: MessagePort
[Historial]
Versión | Cambios |
---|---|
v14.7.0 | Esta clase ahora hereda de EventTarget en lugar de EventEmitter . |
v10.5.0 | Añadido en: v10.5.0 |
- Extiende: <EventTarget>
Las instancias de la clase worker.MessagePort
representan un extremo de un canal de comunicaciones asíncrono y bidireccional. Se puede utilizar para transferir datos estructurados, regiones de memoria y otros MessagePort
s entre diferentes Worker
s.
Esta implementación coincide con los MessagePort
de los navegadores.
Evento: 'close'
Añadido en: v10.5.0
El evento 'close'
se emite una vez que cualquiera de los lados del canal se ha desconectado.
const { MessageChannel } = require('node:worker_threads')
const { port1, port2 } = new MessageChannel()
// Imprime:
// foobar
// ¡cerrado!
port2.on('message', message => console.log(message))
port2.on('close', () => console.log('¡cerrado!'))
port1.postMessage('foobar')
port1.close()
Evento: 'message'
Añadido en: v10.5.0
value
<any> El valor transmitido
El evento 'message'
se emite para cualquier mensaje entrante, que contiene la entrada clonada de port.postMessage()
.
Los listeners en este evento reciben un clon del parámetro value
tal como se pasó a postMessage()
y ningún argumento adicional.
Evento: 'messageerror'
Añadido en: v14.5.0, v12.19.0
error
<Error> Un objeto Error
El evento 'messageerror'
se emite cuando falla la deserialización de un mensaje.
Actualmente, este evento se emite cuando se produce un error al instanciar el objeto JS publicado en el extremo receptor. Estas situaciones son raras, pero pueden ocurrir, por ejemplo, cuando ciertos objetos de la API de Node.js se reciben en un vm.Context
(donde las API de Node.js no están disponibles actualmente).
port.close()
Agregado en: v10.5.0
Deshabilita el envío posterior de mensajes en ambos lados de la conexión. Este método se puede llamar cuando no se producirá más comunicación a través de este MessagePort
.
El evento 'close'
se emite en ambas instancias de MessagePort
que forman parte del canal.
port.postMessage(value[, transferList])
[Historial]
Versión | Cambios |
---|---|
v21.0.0 | Se lanza un error cuando hay un objeto no transferible en la lista de transferencia. |
v15.6.0 | Se agregó X509Certificate a la lista de tipos clonables. |
v15.0.0 | Se agregó CryptoKey a la lista de tipos clonables. |
v15.14.0, v14.18.0 | Se agregó 'BlockList' a la lista de tipos clonables. |
v15.9.0, v14.18.0 | Se agregaron los tipos 'Histogram' a la lista de tipos clonables. |
v14.5.0, v12.19.0 | Se agregó KeyObject a la lista de tipos clonables. |
v14.5.0, v12.19.0 | Se agregó FileHandle a la lista de tipos transferibles. |
v10.5.0 | Agregado en: v10.5.0 |
value
<any>transferList
<Object[]>
Envía un valor de JavaScript al lado receptor de este canal. value
se transfiere de una manera que es compatible con el algoritmo de clonación estructurada HTML.
En particular, las diferencias significativas con JSON
son:
value
puede contener referencias circulares.value
puede contener instancias de tipos JS incorporados comoRegExp
s,BigInt
s,Map
s,Set
s, etc.value
puede contener arreglos con tipo, usandoArrayBuffer
s ySharedArrayBuffer
s.value
puede contener instancias deWebAssembly.Module
.value
no puede contener objetos nativos (con respaldo en C++), excepto:
const { MessageChannel } = require('node:worker_threads')
const { port1, port2 } = new MessageChannel()
port1.on('message', message => console.log(message))
const circularData = {}
circularData.foo = circularData
// Imprime: { foo: [Circular] }
port2.postMessage(circularData)
transferList
puede ser una lista de objetos ArrayBuffer
, MessagePort
y FileHandle
. Después de la transferencia, ya no se pueden usar en el lado emisor del canal (incluso si no están contenidos en value
). A diferencia de con los procesos secundarios, actualmente no se admite la transferencia de identificadores como los sockets de red.
Si value
contiene instancias de SharedArrayBuffer
, se puede acceder a ellas desde cualquiera de los dos hilos. No se pueden incluir en transferList
.
value
aún puede contener instancias de ArrayBuffer
que no están en transferList
; en ese caso, la memoria subyacente se copia en lugar de moverse.
const { MessageChannel } = require('node:worker_threads')
const { port1, port2 } = new MessageChannel()
port1.on('message', message => console.log(message))
const uint8Array = new Uint8Array([1, 2, 3, 4])
// Esto publica una copia de `uint8Array`:
port2.postMessage(uint8Array)
// Esto no copia los datos, pero hace que `uint8Array` sea inutilizable:
port2.postMessage(uint8Array, [uint8Array.buffer])
// Se puede acceder a la memoria para el `sharedUint8Array` desde el
// original y la copia recibida por `.on('message')`:
const sharedUint8Array = new Uint8Array(new SharedArrayBuffer(4))
port2.postMessage(sharedUint8Array)
// Esto transfiere un puerto de mensaje recién creado al receptor.
// Esto se puede utilizar, por ejemplo, para crear canales de comunicación entre
// varios hilos `Worker` que son hijos del mismo hilo principal.
const otherChannel = new MessageChannel()
port2.postMessage({ port: otherChannel.port1 }, [otherChannel.port1])
El objeto de mensaje se clona de inmediato y se puede modificar después de la publicación sin tener efectos secundarios.
Para obtener más información sobre los mecanismos de serialización y deserialización detrás de esta API, consulte la API de serialización del módulo node:v8
.
Consideraciones al transferir TypedArrays y Buffers
Todas las instancias de TypedArray
y Buffer
son vistas sobre un ArrayBuffer
subyacente. Es decir, es el ArrayBuffer
el que realmente almacena los datos sin procesar, mientras que los objetos TypedArray
y Buffer
proporcionan una forma de ver y manipular los datos. Es posible y común que se creen múltiples vistas sobre la misma instancia de ArrayBuffer
. Se debe tener mucho cuidado al utilizar una lista de transferencia para transferir un ArrayBuffer
, ya que hacerlo provoca que todas las instancias de TypedArray
y Buffer
que comparten el mismo ArrayBuffer
se vuelvan inutilizables.
const ab = new ArrayBuffer(10)
const u1 = new Uint8Array(ab)
const u2 = new Uint16Array(ab)
console.log(u2.length) // imprime 5
port.postMessage(u1, [u1.buffer])
console.log(u2.length) // imprime 0
Para las instancias de Buffer
, específicamente, si el ArrayBuffer
subyacente se puede transferir o clonar depende totalmente de cómo se crearon las instancias, lo que a menudo no se puede determinar de manera confiable.
Un ArrayBuffer
se puede marcar con markAsUntransferable()
para indicar que siempre se debe clonar y nunca transferir.
Dependiendo de cómo se creó una instancia de Buffer
, puede que sea propietaria o no de su ArrayBuffer
subyacente. Un ArrayBuffer
no se debe transferir a menos que se sepa que la instancia de Buffer
es propietaria del mismo. En particular, para los Buffer
creados a partir del pool interno de Buffer
(utilizando, por ejemplo, Buffer.from()
o Buffer.allocUnsafe()
), no es posible transferirlos y siempre se clonan, lo que envía una copia de todo el pool de Buffer
. Este comportamiento puede conllevar un mayor uso de memoria no deseado y posibles problemas de seguridad.
Consulte Buffer.allocUnsafe()
para obtener más detalles sobre el pooling de Buffer
.
Los ArrayBuffer
para las instancias de Buffer
creadas utilizando Buffer.alloc()
o Buffer.allocUnsafeSlow()
siempre se pueden transferir, pero hacerlo hace que todas las demás vistas existentes de esos ArrayBuffer
sean inutilizables.
Consideraciones al clonar objetos con prototipos, clases y descriptores de acceso
Debido a que la clonación de objetos utiliza el algoritmo de clonación estructurada HTML, las propiedades no enumerables, los descriptores de acceso de propiedades y los prototipos de objetos no se conservan. En particular, los objetos Buffer
se leerán como Uint8Array
s simples en el lado receptor, y las instancias de clases JavaScript se clonarán como objetos JavaScript simples.
const b = Symbol('b')
class Foo {
#a = 1
constructor() {
this[b] = 2
this.c = 3
}
get d() {
return 4
}
}
const { port1, port2 } = new MessageChannel()
port1.onmessage = ({ data }) => console.log(data)
port2.postMessage(new Foo())
// Imprime: { c: 3 }
Esta limitación se extiende a muchos objetos incorporados, como el objeto global URL
:
const { port1, port2 } = new MessageChannel()
port1.onmessage = ({ data }) => console.log(data)
port2.postMessage(new URL('https://example.org'))
// Imprime: { }
port.hasRef()
Agregado en: v18.1.0, v16.17.0
[Estable: 1 - Experimental]
Estable: 1 Estabilidad: 1 - Experimental
- Devuelve: <boolean>
Si es verdadero, el objeto MessagePort
mantendrá activo el bucle de eventos de Node.js.
port.ref()
Agregado en: v10.5.0
Opuesto a unref()
. Llamar a ref()
en un puerto previamente unref()
no permite que el programa se cierre si es el único controlador activo que queda (el comportamiento predeterminado). Si el puerto está ref()
, llamar a ref()
nuevamente no tiene efecto.
Si los listeners se adjuntan o se eliminan usando .on('message')
, el puerto se ref()
y se unref()
automáticamente dependiendo de si existen listeners para el evento.
port.start()
Agregado en: v10.5.0
Comienza a recibir mensajes en este MessagePort
. Al usar este puerto como un emisor de eventos, esto se llama automáticamente una vez que se adjuntan los listeners 'message'
.
Este método existe para la paridad con la API MessagePort
Web. En Node.js, solo es útil para ignorar mensajes cuando no hay un listener de eventos presente. Node.js también diverge en su manejo de .onmessage
. Al establecerlo, se llama automáticamente a .start()
, pero al anularlo, los mensajes se ponen en cola hasta que se establece un nuevo controlador o se descarta el puerto.
port.unref()
Agregado en: v10.5.0
Llamar a unref()
en un puerto permite que el hilo salga si este es el único controlador activo en el sistema de eventos. Si el puerto ya está en unref()
ed, llamar a unref()
nuevamente no tiene efecto.
Si los listeners se adjuntan o se eliminan utilizando .on('message')
, el puerto se ref()
y unref()
automáticamente dependiendo de si existen listeners para el evento.
Clase: Worker
Agregado en: v10.5.0
- Extiende: <EventEmitter>
La clase Worker
representa un hilo de ejecución de JavaScript independiente. La mayoría de las API de Node.js están disponibles dentro de este.
Las diferencias notables dentro de un entorno Worker son:
- Los flujos
process.stdin
,process.stdout
yprocess.stderr
pueden ser redirigidos por el hilo principal. - La propiedad
require('node:worker_threads').isMainThread
se establece enfalse
. - El puerto de mensajes
require('node:worker_threads').parentPort
está disponible. process.exit()
no detiene todo el programa, solo el hilo único, yprocess.abort()
no está disponible.process.chdir()
y los métodosprocess
que establecen ids de grupo o usuario no están disponibles.process.env
es una copia de las variables de entorno del hilo principal, a menos que se especifique lo contrario. Los cambios en una copia no son visibles en otros hilos, y no son visibles para los complementos nativos (a menos que se paseworker.SHARE_ENV
como la opciónenv
al constructorWorker
). En Windows, a diferencia del hilo principal, una copia de las variables de entorno opera de manera sensible a mayúsculas y minúsculas.process.title
no se puede modificar.- Las señales no se entregan a través de
process.on('...')
. - La ejecución puede detenerse en cualquier momento como resultado de que se invoque
worker.terminate()
. - Los canales IPC de los procesos principales no son accesibles.
- El módulo
trace_events
no es compatible. - Los complementos nativos solo se pueden cargar desde múltiples hilos si cumplen ciertas condiciones.
Es posible crear instancias de Worker
dentro de otros Worker
s.
Al igual que los Web Workers y el módulo node:cluster
, la comunicación bidireccional se puede lograr mediante el paso de mensajes entre hilos. Internamente, un Worker
tiene un par incorporado de MessagePort
s que ya están asociados entre sí cuando se crea el Worker
. Si bien el objeto MessagePort
del lado principal no se expone directamente, sus funcionalidades se exponen a través de worker.postMessage()
y el evento worker.on('message')
en el objeto Worker
para el hilo principal.
Para crear canales de mensajería personalizados (lo que se recomienda en lugar de usar el canal global predeterminado porque facilita la separación de preocupaciones), los usuarios pueden crear un objeto MessageChannel
en cualquiera de los hilos y pasar uno de los MessagePort
s en ese MessageChannel
al otro hilo a través de un canal preexistente, como el global.
Consulte port.postMessage()
para obtener más información sobre cómo se pasan los mensajes y qué tipo de valores de JavaScript se pueden transportar con éxito a través de la barrera de hilos.
const assert = require('node:assert')
const { Worker, MessageChannel, MessagePort, isMainThread, parentPort } = require('node: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('recibido:', value)
})
} else {
parentPort.once('message', value => {
assert(value.hereIsYourPort instanceof MessagePort)
value.hereIsYourPort.postMessage('el worker está enviando esto')
value.hereIsYourPort.close()
})
}
new Worker(filename[, options])
[Historial]
Versión | Cambios |
---|---|
v19.8.0, v18.16.0 | Se añadió soporte para una opción name , que permite agregar un nombre al título del worker para depuración. |
v14.9.0 | El parámetro filename puede ser un objeto URL WHATWG utilizando el protocolo data: . |
v14.9.0 | La opción trackUnmanagedFds se estableció en true de forma predeterminada. |
v14.6.0, v12.19.0 | Se introdujo la opción trackUnmanagedFds . |
v13.13.0, v12.17.0 | Se introdujo la opción transferList . |
v13.12.0, v12.17.0 | El parámetro filename puede ser un objeto URL WHATWG utilizando el protocolo file: . |
v13.4.0, v12.16.0 | Se introdujo la opción argv . |
v13.2.0, v12.16.0 | Se introdujo la opción resourceLimits . |
v10.5.0 | Agregado en: v10.5.0 |
filename
<string> | <URL> La ruta al script principal o módulo del Worker. Debe ser una ruta absoluta o una ruta relativa (es decir, relativa al directorio de trabajo actual) que comience con./
o../
, o un objetoURL
WHATWG utilizando el protocolofile:
odata:
. Cuando se utiliza unaURL data:
, los datos se interpretan basándose en el tipo MIME utilizando el cargador de módulos ECMAScript. Sioptions.eval
estrue
, esto es una cadena que contiene código JavaScript en lugar de una ruta.options
<Object>argv
<any[]> Lista de argumentos que se convertirían en cadena y se anexarían aprocess.argv
en el worker. Esto es muy similar aworkerData
, pero los valores están disponibles en elprocess.argv
global como si se pasaran como opciones de la CLI al script.env
<Object> Si se establece, especifica el valor inicial deprocess.env
dentro del hilo Worker. Como valor especial, se puede utilizarworker.SHARE_ENV
para especificar que el hilo padre y el hilo hijo deben compartir sus variables de entorno; en ese caso, los cambios en el objetoprocess.env
de un hilo también afectan al otro hilo. Predeterminado:process.env
.eval
<boolean> Si estrue
y el primer argumento es unastring
, interpreta el primer argumento del constructor como un script que se ejecuta una vez que el worker está en línea.execArgv
<string[]> Lista de opciones de la CLI de node pasadas al worker. Las opciones de V8 (como--max-old-space-size
) y las opciones que afectan al proceso (como--title
) no son compatibles. Si se establece, esto se proporciona comoprocess.execArgv
dentro del worker. De forma predeterminada, las opciones se heredan del hilo padre.stdin
<boolean> Si esto se establece entrue
, entoncesworker.stdin
proporciona un flujo grabable cuyo contenido aparece comoprocess.stdin
dentro del Worker. De forma predeterminada, no se proporcionan datos.stdout
<boolean> Si esto se establece entrue
, entoncesworker.stdout
no se canaliza automáticamente aprocess.stdout
en el padre.stderr
<boolean> Si esto se establece entrue
, entoncesworker.stderr
no se canaliza automáticamente aprocess.stderr
en el padre.workerData
<any> Cualquier valor de JavaScript que se clona y está disponible comorequire('node:worker_threads').workerData
. La clonación se produce tal como se describe en el algoritmo de clonación estructurada HTML, y se produce un error si el objeto no se puede clonar (por ejemplo, porque contienefunction
s).trackUnmanagedFds
<boolean> Si esto se establece entrue
, entonces el Worker rastrea los descriptores de archivos sin administrar gestionados a través defs.open()
yfs.close()
, y los cierra cuando el Worker sale, de forma similar a otros recursos como los sockets de red o los descriptores de archivos gestionados a través de la APIFileHandle
. Esta opción es automáticamente heredada por todos losWorker
anidados. Predeterminado:true
.transferList
<Object[]> Si uno o más objetos similares aMessagePort
se pasan enworkerData
, se requiere unatransferList
para esos elementos o se lanzaERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST
. Consulteport.postMessage()
para obtener más información.resourceLimits
<Object> Un conjunto opcional de límites de recursos para la nueva instancia del motor JS. Alcanzar estos límites conduce a la terminación de la instancia deWorker
. Estos límites solo afectan al motor JS, y no a datos externos, incluyendo ningúnArrayBuffer
s. Incluso si estos límites están establecidos, el proceso aún puede abortar si encuentra una situación global de falta de memoria.maxOldGenerationSizeMb
<number> El tamaño máximo del heap principal en MB. Si se establece el argumento de la línea de comandos--max-old-space-size
, este valor anula esta configuración.maxYoungGenerationSizeMb
<number> El tamaño máximo de un espacio de heap para objetos creados recientemente. Si se establece el argumento de la línea de comandos--max-semi-space-size
, este valor anula esta configuración.codeRangeSizeMb
<number> El tamaño de un rango de memoria preasignado utilizado para código generado.stackSizeMb
<number> El tamaño máximo predeterminado de la pila para el hilo. Los valores pequeños pueden llevar a instancias de Worker inutilizables. Predeterminado:4
.name
<string> Unname
opcional para anexar al título del worker con fines de depuración/identificación, haciendo que el título final sea[worker ${id}] ${name}
. Predeterminado:''
.
Evento: 'error'
Agregado en: v10.5.0
err
<Error>
El evento 'error'
se emite si el hilo de trabajo lanza una excepción no detectada. En ese caso, el trabajador se termina.
Evento: 'exit'
Agregado en: v10.5.0
exitCode
<integer>
El evento 'exit'
se emite una vez que el trabajador se ha detenido. Si el trabajador salió llamando a process.exit()
, el parámetro exitCode
es el código de salida pasado. Si el trabajador fue terminado, el parámetro exitCode
es 1
.
Este es el evento final emitido por cualquier instancia de Worker
.
Evento: 'message'
Agregado en: v10.5.0
value
<any> El valor transmitido
El evento 'message'
se emite cuando el hilo de trabajo ha invocado require('node:worker_threads').parentPort.postMessage()
. Consulte el evento port.on('message')
para obtener más detalles.
Todos los mensajes enviados desde el hilo de trabajo se emiten antes de que se emita el evento 'exit'
en el objeto Worker
.
Evento: 'messageerror'
Agregado en: v14.5.0, v12.19.0
error
<Error> Un objeto Error
El evento 'messageerror'
se emite cuando la deserialización de un mensaje falla.
Evento: 'online'
Agregado en: v10.5.0
El evento 'online'
se emite cuando el hilo de trabajo ha comenzado a ejecutar código JavaScript.
worker.getHeapSnapshot([options])
[Historial]
Versión | Cambios |
---|---|
v19.1.0 | Soporte para opciones para configurar la instantánea del montón. |
v13.9.0, v12.17.0 | Agregado en: v13.9.0, v12.17.0 |
options
<Objeto>exposeInternals
<booleano> Si es true, expone los internos en la instantánea del montón. Predeterminado:false
.exposeNumericValues
<booleano> Si es true, expone los valores numéricos en campos artificiales. Predeterminado:false
.
Retorna: <Promise> Una promesa para un Readable Stream que contiene una instantánea del montón de V8.
Devuelve un flujo legible para una instantánea de V8 del estado actual del Worker. Consulte v8.getHeapSnapshot()
para obtener más detalles.
Si el hilo de Worker ya no se está ejecutando, lo que puede ocurrir antes de que se emita el evento 'exit'
, la Promise
devuelta se rechaza inmediatamente con un error ERR_WORKER_NOT_RUNNING
.
worker.performance
Agregado en: v15.1.0, v14.17.0, v12.22.0
Un objeto que se puede utilizar para consultar información de rendimiento desde una instancia de trabajador. Similar a perf_hooks.performance
.
performance.eventLoopUtilization([utilization1[, utilization2]])
Agregado en: v15.1.0, v14.17.0, v12.22.0
utilization1
<Objeto> El resultado de una llamada anterior aeventLoopUtilization()
.utilization2
<Objeto> El resultado de una llamada anterior aeventLoopUtilization()
antes deutilization1
.- Devuelve: <Objeto>
La misma llamada que perf_hooks
eventLoopUtilization()
, excepto que se devuelven los valores de la instancia del trabajador.
Una diferencia es que, a diferencia del hilo principal, el bootstrapping dentro de un trabajador se realiza dentro del bucle de eventos. Por lo tanto, la utilización del bucle de eventos está disponible de inmediato una vez que el script del trabajador comienza la ejecución.
Un tiempo idle
que no aumenta no indica que el trabajador esté atascado en el bootstrap. Los siguientes ejemplos muestran cómo la vida útil completa del trabajador nunca acumula ningún tiempo idle
, pero aún puede procesar mensajes.
const { Worker, isMainThread, parentPort } = require('node:worker_threads')
if (isMainThread) {
const worker = new Worker(__filename)
setInterval(() => {
worker.postMessage('hi')
console.log(worker.performance.eventLoopUtilization())
}, 100).unref()
return
}
parentPort.on('message', () => console.log('msg')).unref()
;(function r(n) {
if (--n < 0) return
const t = Date.now()
while (Date.now() - t < 300);
setImmediate(r, n)
})(10)
La utilización del bucle de eventos de un trabajador está disponible solo después de que se emita el evento 'online'
, y si se llama antes de esto, o después del evento 'exit'
, entonces todas las propiedades tienen el valor de 0
.
worker.postMessage(value[, transferList])
Añadido en: v10.5.0
value
<any>transferList
<Object[]>
Envía un mensaje al worker que se recibe a través de require('node:worker_threads').parentPort.on('message')
. Consulte port.postMessage()
para obtener más detalles.
worker.ref()
Añadido en: v10.5.0
Opuesto a unref()
, llamar a ref()
en un worker previamente unref()
ed no permite que el programa salga si es el único controlador activo restante (el comportamiento predeterminado). Si el worker está ref()
ed, llamar a ref()
nuevamente no tiene ningún efecto.
worker.resourceLimits
Añadido en: v13.2.0, v12.16.0
Proporciona el conjunto de restricciones de recursos del motor JS para este hilo de Worker. Si la opción resourceLimits
se pasó al constructor Worker
, esto coincide con sus valores.
Si el worker se ha detenido, el valor de retorno es un objeto vacío.
worker.stderr
Agregado en: v10.5.0
Este es un flujo legible que contiene datos escritos en process.stderr
dentro del hilo de trabajo. Si no se pasó stderr: true
al constructor Worker
, entonces los datos se canalizan al flujo process.stderr
del hilo principal.
worker.stdin
Agregado en: v10.5.0
Si se pasó stdin: true
al constructor Worker
, este es un flujo de escritura. Los datos escritos en este flujo estarán disponibles en el hilo de trabajo como process.stdin
.
worker.stdout
Agregado en: v10.5.0
Este es un flujo legible que contiene datos escritos en process.stdout
dentro del hilo de trabajo. Si no se pasó stdout: true
al constructor Worker
, entonces los datos se canalizan al flujo process.stdout
del hilo principal.
worker.terminate()
[Historial]
Versión | Cambios |
---|---|
v12.5.0 | Esta función ahora devuelve una Promesa. Pasar una función de devolución de llamada está obsoleto, y era inútil hasta esta versión, ya que el Worker se terminaba realmente de forma síncrona. La terminación es ahora una operación totalmente asíncrona. |
v10.5.0 | Añadido en: v10.5.0 |
- Devuelve: <Promesa>
Detiene toda la ejecución de JavaScript en el hilo de trabajo lo antes posible. Devuelve una Promesa para el código de salida que se cumple cuando se emite el evento 'exit'
.
worker.threadId
Añadido en: v10.5.0
Un identificador entero para el hilo referenciado. Dentro del hilo de trabajo, está disponible como require('node:worker_threads').threadId
. Este valor es único para cada instancia Worker
dentro de un solo proceso.
worker.unref()
Agregado en: v10.5.0
Llamar a unref()
en un worker permite que el hilo se cierre si este es el único manejador activo en el sistema de eventos. Si el worker ya está unref()
llamado, llamar a unref()
de nuevo no tiene ningún efecto.
Notas
Bloqueo síncrono de stdio
Los Worker
s utilizan el paso de mensajes a través de <MessagePort> para implementar interacciones con stdio
. Esto significa que la salida de stdio
originada en un Worker
puede ser bloqueada por código síncrono en el extremo receptor que está bloqueando el bucle de eventos de Node.js.
import { Worker, isMainThread } from 'node:worker_threads'
if (isMainThread) {
new Worker(new URL(import.meta.url))
for (let n = 0; n < 1e10; n++) {
// Bucle para simular trabajo.
}
} else {
// Esta salida será bloqueada por el bucle for en el hilo principal.
console.log('foo')
}
'use strict'
const { Worker, isMainThread } = require('node:worker_threads')
if (isMainThread) {
new Worker(__filename)
for (let n = 0; n < 1e10; n++) {
// Bucle para simular trabajo.
}
} else {
// Esta salida será bloqueada por el bucle for en el hilo principal.
console.log('foo')
}
Lanzar hilos de trabajador desde scripts de precarga
Tenga cuidado al lanzar hilos de trabajador desde scripts de precarga (scripts cargados y ejecutados usando el indicador de línea de comandos -r
). A menos que la opción execArgv
se establezca explícitamente, los nuevos hilos de trabajador heredan automáticamente los indicadores de línea de comandos del proceso en ejecución y precargarán los mismos scripts de precarga que el hilo principal. Si el script de precarga lanza incondicionalmente un hilo de trabajador, cada hilo generado generará otro hasta que la aplicación se bloquee.