🚀🚀workerpool,JavaScript强大的线程池库!
workerpool:Node.js和浏览器中的任务分发利器
原文链接:https://github.com/josdejong/workerpool
作者:Jos de Jong
译者:倔强青铜三
前言
大家好,我是倔强青铜三。是一名热情的软件工程师,我热衷于分享和传播IT技术,致力于通过我的知识和技能推动技术交流与创新,欢迎关注我,微信公众号:倔强青铜三。欢迎点赞、收藏、关注,一键三连!!!
workerpool是一个强大的库,它为Node.js和浏览器环境提供了一种简单的方式来创建任务分发池。通过这个库,你可以轻松地将计算密集型任务卸载到一个工作线程池中,从而避免阻塞主线程,提高应用程序的响应性和性能。无论你是前端开发者还是Node.js服务器端开发者,workerpool都能为你提供一个高效的解决方案来处理并发任务。
特性
- 易于使用:简单几行代码即可上手。
- 跨平台运行:既可在浏览器中运行,也可在Node.js环境中使用。
- 动态卸载函数:可以将函数动态地卸载到工作线程中执行。
- 通过代理访问工作线程:提供了一种自然的、基于Promise的代理方式来访问工作线程,就像它们直接在主应用程序中可用一样。
- 取消运行中的任务:能够取消正在执行的任务。
- 设置任务超时:可以为任务设置超时时间。
- 处理崩溃的工作线程:能够妥善处理工作线程崩溃的情况。
- 体积小巧:仅9 kB(minified and gzipped)。
- 支持可转移对象:仅限于Web Workers和worker_threads。
为什么需要workerpool
JavaScript基于单个事件循环,一次只能处理一个事件。在Node.js中,虽然所有I/O代码都是非阻塞的,但所有非I/O代码却是阻塞的。这意味着CPU密集型任务会阻塞其他任务的执行。在浏览器环境中,执行CPU密集型任务时,浏览器不会响应用户事件(如鼠标点击),导致浏览器“卡住”。在Node.js服务器端,执行单个重量级请求时,服务器不会响应任何新请求。因此,为了提高前端进程的用户体验,CPU密集型任务应该从主事件循环中卸载到专门的工作线程上。在浏览器环境中可以使用Web Workers,在Node.js中则可以使用子进程和worker_threads。将应用程序拆分成独立的、解耦的部分,这些部分可以以并行化的方式独立运行,从而实现通过隔离进程和消息传递来达到并发的架构。
安装
通过npm安装:
npm install workerpool
加载
在Node.js应用程序中加载workerpool(无论是主应用程序还是工作线程):
const workerpool = require('workerpool');
在浏览器中加载workerpool:
<script src="workerpool.js"></script>
在浏览器的Web Worker中加载workerpool:
importScripts('workerpool.js');
在React或webpack5中设置workerpool需要额外的配置步骤,具体请参考webpack5部分的说明。
使用方法
动态卸载函数
以下示例中有一个add
函数,它被动态地卸载到工作线程中执行给定的一组参数。
myApp.js
const workerpool = require('workerpool');
const pool = workerpool.pool();
function add(a, b) {
return a + b;
}
pool
.exec(add, [3, 4])
.then(function (result) {
console.log('result', result); // 输出7
})
.catch(function (err) {
console.error(err);
})
.then(function () {
pool.terminate(); // 完成后终止所有工作线程
});
请注意,函数和参数都必须是静态的,并且可以被序列化,因为它们需要以序列化的形式发送到工作线程。对于大型函数或函数参数,将数据发送到工作线程的开销可能会很大。
专用工作线程
可以在单独的脚本中创建一个专用工作线程,然后通过工作线程池来使用它。
myWorker.js
const workerpool = require('workerpool');
// 故意低效的斐波那契数列实现
function fibonacci(n) {
if (n < 2) return n;
return fibonacci(n - 2) + fibonacci(n - 1);
}
// 创建工作线程并注册公共函数
workerpool.worker({
fibonacci: fibonacci,
});
这个工作线程可以通过工作线程池来使用:
myApp.js
const workerpool = require('workerpool');
// 使用外部工作线程脚本创建工作线程池
const pool = workerpool.pool(__dirname + '/myWorker.js');
// 通过exec在工作线程上运行注册的函数
pool
.exec('fibonacci', [10])
.then(function (result) {
console.log('Result: ' + result); // 输出55
})
.catch(function (err) {
console.error(err);
})
.then(function () {
pool.terminate(); // 完成后终止所有工作线程
});
// 或者通过代理在工作线程上运行注册的函数:
pool
.proxy()
.then(function (worker) {
return worker.fibonacci(10);
})
.then(function (result) {
console.log('Result: ' + result); // 输出55
})
.catch(function (err) {
console.error(err);
})
.then(function () {
pool.terminate(); // 完成后终止所有工作线程
});
工作线程也可以异步初始化:
myAsyncWorker.js
define(['workerpool/dist/workerpool'], function (workerpool) {
// 故意低效的斐波那契数列实现
function fibonacci(n) {
if (n < 2) return n;
return fibonacci(n - 2) + fibonacci(n - 1);
}
// 创建工作线程并注册公共函数
workerpool.worker({
fibonacci: fibonacci,
});
});
示例
示例位于examples目录中:
https://github.com/josdejong/workerpool/tree/master/examples
API
workerpool的API包含两部分:一个用于创建工作线程池的函数workerpool.pool
,以及一个用于创建工作线程的函数workerpool.worker
。
pool
使用函数workerpool.pool
可以创建工作线程池:
workerpool.pool([script: string] [, options: Object]) : Pool
当提供script
参数时,提供的脚本将作为专用工作线程启动。如果没有提供script
参数,则会启动一个默认工作线程,可以通过Pool.exec
动态卸载函数。在Node.js中,script
必须是绝对文件路径,例如__dirname + '/myWorker.js'
。在浏览器环境中,script
也可以是data URL,例如'data:application/javascript;base64,...'
。这允许将工作线程的打包代码嵌入到主应用程序中。请参见examples/embeddedWorker
中的演示。
可用的选项如下:
minWorkers: number | 'max'
。必须初始化并保持可用的最小工作线程数量。将其设置为'max'
将创建maxWorkers
默认工作线程(见下文)。maxWorkers: number
。默认的maxWorkers
数量是CPU数量减一。如果无法确定CPU数量(例如在旧浏览器中),maxWorkers
设置为3。maxQueueSize: number
。允许排队的任务的最大数量。可以用来防止内存耗尽。如果超过最大值,添加新任务将抛出错误。默认值为Infinity
。workerType: 'auto' | 'web' | 'process' | 'thread'
。- 如果是
'auto'
(默认值),workerpool将自动选择合适类型的工作线程:在浏览器环境中使用'web'
。在Node.js环境中,如果可用(Node.js >= 11.7.0),则使用worker_threads
,否则使用child_process
。 - 如果是
'web'
,则使用Web Worker。仅在浏览器环境中可用。 - 如果是
'process'
,则使用child_process
。仅在Node.js环境中可用。 - 如果是
'thread'
,则使用worker_threads
。如果worker_threads
不可用,则抛出错误。仅在Node.js环境中可用。
- 如果是
workerTerminateTimeout: number
。终止工作线程时等待工作线程清理资源的超时时间(以毫秒为单位)。默认值为1000
。abortListenerTimeout: number
。等待中止监听器的超时时间(以毫秒为单位),超时后将强制停止并触发清理。默认值为1000
。forkArgs: String[]
。对于process
工作线程类型。作为args
传递给child_process.fork
的数组。forkOpts: Object
。对于process
工作线程类型。作为options
传递给child_process.fork
的对象。请参见Node.js文档以了解可用选项。workerOpts: Object
。对于web
工作线程类型。传递给Web Worker构造函数的对象。请参见WorkerOptions规范以了解可用选项。workerThreadOpts: Object
。对于worker
工作线程类型。传递给worker_threads.options
的对象。请参见Node.js文档以了解可用选项。onCreateWorker: Function
。每当创建工作线程时调用的回调。它可以用来为每个工作线程分配资源,例如。回调的参数是一个具有以下属性的对象:forkArgs: String[]
:此池的forkArgs
选项。forkOpts: Object
:此池的forkOpts
选项。workerOpts: Object
:此池的workerOpts
选项。script: string
:此池的script
选项。
可选地,此回调可以返回一个对象,包含上述一个或多个属性。提供的属性将用于覆盖正在创建工作线程的池属性。
onTerminateWorker: Function
。每当终止工作线程时调用的回调。它可以用来释放可能为这个特定工作线程分配的资源。回调的参数是一个为onCreateWorker
描述的对象,每个属性都设置为正在终止的工作线程的值。emitStdStreams: boolean
。对于process
或thread
工作线程类型。如果为true
,工作线程将发出stdout
和stderr
事件,而不是将其传递到父流。默认值为false
。
关于'workerType'
的重要说明:当从和向工作线程发送和接收原始数据类型(纯JSON)时,不同的工作线程类型('web'
、'process'
、'thread'
)可以互换使用。但是,当使用更高级的数据类型(如缓冲区)时,API和返回结果可能会有所不同。在这种情况下,最好不要使用'auto'
设置,而是有一个固定的'workerType'
,并有良好的单元测试。
工作线程池包含以下函数:
Pool.exec(method: Function | string, params: Array | null [, options: Object]) : Promise<any, Error>
执行工作线程上的函数,并给出参数。
- 当
method
是字符串时,工作线程上必须存在具有此名称的方法,并且必须注册以使其可以通过池访问。该函数将在工作线程上执行,并给出参数。 - 当
method
是函数时,提供的函数fn
将被序列化,发送到工作线程,并在那里与提供的参数一起执行。提供的函数必须是静态的,它不能依赖于周围作用域中的变量。 可用的选项如下:
on: (payload: any) => void
。事件监听器,用于处理工作线程为此次执行发送的事件。请参见事件部分以了解更多信息。transfer: Object[]
。要发送到工作线程的可转移对象列表。process
工作线程类型不支持此选项。请参见示例以了解用法。
Pool.proxy() : Promise<Object, Error>
创建工作线程池的代理。代理包含工作线程上所有方法的代理。所有方法返回的都是解析方法结果的Promise。
Pool.stats() : Object
获取工作线程、活跃任务和待处理任务的统计信息。
返回一个包含以下属性的对象:
{
totalWorkers: 0,
busyWorkers: 0,
idleWorkers: 0,
pendingTasks: 0,
activeTasks: 0
}
Pool.terminate([force: boolean [, timeout: number]]) : Promise<void, Error>
如果参数force
为false
(默认值),工作线程将完成它们正在处理的任务,然后自行终止。任何待处理的任务将被拒绝,并抛出错误'Pool terminated'
。当force
为true
时,所有工作线程将立即终止,而不完成正在运行的任务。如果提供了timeout
,当超时到期且工作线程尚未完成时,将强制终止工作线程。
Pool.exec
函数和代理函数都返回一个Promise
。Promise具有以下函数可用:
Promise.then(fn: Function<result: any>) : Promise<any, Error>
获取Promise解析后的结果。
Promise.catch(fn: Function<error: Error>) : Promise<any, Error>
获取Promise拒绝时的错误。
Promise.finally(fn: Function<void>)
无论Promise是解析
还是拒绝
,都会执行的逻辑。
Promise.cancel() : Promise<any, Error>
可以取消正在运行的任务。执行任务的工作线程将被强制立即终止。Promise将被拒绝,并抛出Promise.CancellationError
。
Promise.timeout(delay: number) : Promise<any, Error>
如果任务在给定的延迟(以毫秒为单位)内未解析或拒绝,则取消正在运行的任务。计时器将在任务实际开始时启动,而不是在任务创建并排队时启动。执行任务的工作线程将被强制立即终止。Promise将被拒绝,并抛出Promise.TimeoutError
。
示例用法:
const workerpool = require('workerpool');
function add(a, b) {
return a + b;
}
const pool1 = workerpool.pool();
// 将函数卸载到工作线程
pool1
.exec(add, [2, 4])
.then(function (result) {
console.log(result); // 将输出6
})
.catch(function (err) {
console.error(err);
});
// 创建专用工作线程
const pool2 = workerpool.pool(__dirname + '/myWorker.js');
// 假设myWorker.js包含一个名为'fibonacci'的函数
pool2
.exec('fibonacci', [10])
.then(function (result) {
console.log(result); // 将输出55
})
.catch(function (err) {
console.error(err);
});
// 向工作线程发送可转移对象
// 假设myWorker.js包含一个名为'sum'的函数
const toTransfer = new Uint8Array(2).map((_v, i) => i);
pool2
.exec('sum', [toTransfer], { transfer: [toTransfer.buffer] })
.then(function (result) {
console.log(result); // 将输出3
})
.catch(function (err) {
console.error(err);
});
// 创建myWorker.js的代理
pool2
.proxy()
.then(function (myWorker) {
return myWorker.fibonacci(10);
})
.then(function (result) {
console.log(result); // 将输出55
})
.catch(function (err) {
console.error(err);
});
// 创建具有指定最大工作线程数量的池
const pool3 = workerpool.pool({ maxWorkers: 7 });
worker
工作线程的构造方式如下:
workerpool.worker([methods: Object<String, Function>] [, options: Object]) : void
methods
参数是可选的,可以是一个对象,包含工作线程中可用的函数。注册的函数将通过工作线程池可用。
可用的选项如下:
onTerminate: ([code: number]) => Promise<void> | void
。每当终止工作线程时调用的回调。它可以用来释放可能为这个特定工作线程分配的资源。与池的onTerminateWorker
的区别是,此回调在工作线程上下文中运行,而onTerminateWorker
在主线程上执行。
示例用法:
// 文件myWorker.js
const workerpool = require('workerpool');
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
// 创建工作线程并注册函数
workerpool.worker({
add: add,
multiply: multiply,
});
工作线程中的函数可以通过返回Promise来处理异步结果:
// 文件myWorker.js
const workerpool = require('workerpool');
function timeout(delay) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, delay);
});
}
// 创建工作线程并注册函数
workerpool.worker({
timeout: timeout,
});
可以使用Transfer
辅助类将可转移对象发送回池:
// 文件myWorker.js
const workerpool = require('workerpool');
function array(size) {
var array = new Uint8Array(size).map((_v, i) => i);
return new workerpool.Transfer(array, [array.buffer]);
}
// 创建工作线程并注册函数
workerpool.worker({
array: array,
});
事件
可以使用workerEmit
函数从工作线程向池发送数据,而任务正在执行:
workerEmit(payload: any) : unknown
此函数仅在工作线程内部以及在任务执行期间有效。
示例:
// 文件myWorker.js
const workerpool = require('workerpool');
function eventExample(delay) {
workerpool.workerEmit({
status: 'in_progress',
});
workerpool.workerEmit({
status: 'complete',
});
return true;
}
// 创建工作线程并注册函数
workerpool.worker({
eventExample: eventExample,
});
要接收这些事件,可以使用池exec
方法的on
选项:
pool.exec('eventExample', [], {
on: function (payload) {
if (payload.status === 'in_progress') {
console.log('In progress...');
} else if (payload.status === 'complete') {
console.log('Done!');
}
},
});
工作线程API
工作线程可以访问worker
API,其中包含以下方法:
emit: (payload: unknown | Transfer): void
addAbortListener: (listener: () => Promise<void>): void
可以通过worker.addAbortListener
注册中止监听器
,工作线程终止可能是可恢复的。如果所有注册的监听器都已解决,则工作线程将不会被终止,允许在某些情况下重用工作线程。
注意:为了成功清理操作,工作线程实现应该是异步的。如果工作线程被阻塞,则工作线程将被杀死。
function asyncTimeout() {
var me = this;
return new Promise(function (resolve) {
let timeout = setTimeout(() => {
resolve();
}, 5000);
// 注册一个监听器,它将在上面的超时触发之前解决。
me.worker.addAbortListener(async function () {
clearTimeout(timeout);
resolve();
});
});
}
// 创建工作线程并注册公共函数
workerpool.worker(
{
asyncTimeout: asyncTimeout,
},
{
abortListenerTimeout: 1000
}
);
也可以通过worker.emit
从worker
API发出事件:
// 文件myWorker.js
const workerpool = require('workerpool');
function eventExample(delay) {
this.worker.emit({
status: "in_progress",
});
workerpool.workerEmit({
status: 'complete',
});
return true;
}
// 创建工作线程并注册函数
workerpool.worker({
eventExample: eventExample,
});
实用工具
提供了以下属性以方便使用:
- platform:JavaScript平台。要么是_node_,要么是_browser_。
- isMainThread:代码是否在主线程中运行(工作线程不是)。
- cpus:可用的CPU/核心数量。
路线图
- 实现并行处理函数:
map
、reduce
、forEach
、filter
、some
、every
等。 - 在不支持Web Workers的旧浏览器上实现优雅降级:回退到在主应用程序中处理任务。
- 实现会话支持:能够由单个工作线程处理一系列相关任务,该工作线程可以为会话保持状态。
相关库
- https://github.com/andywer/threads.js
- https://github.com/piscinajs/piscina
- https://github.com/learnboost/cluster
- https://github.com/adambom/parallel.js
- https://github.com/padolsey/operative
- https://github.com/calvinmetcalf/catiline
- https://github.com/Unitech/pm2
- https://github.com/godaddy/node-cluster-service
- https://github.com/ramesaliyev/EasyWebWorker
- https://github.com/rvagg/node-worker-farm
最后感谢阅读!欢迎关注我,微信公众号:倔强青铜三。欢迎点赞
、收藏
、关注
,一键三连!!!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。