Новый модуль HeavyCalc для тяжелых вычислений в отдельном потоке
This commit is contained in:
133
server/core/HeavyCalc.js
Normal file
133
server/core/HeavyCalc.js
Normal file
@@ -0,0 +1,133 @@
|
||||
const { Worker } = require('worker_threads');
|
||||
|
||||
class CalcThread {
|
||||
constructor() {
|
||||
this.worker = null;
|
||||
this.listeners = new Map();
|
||||
this.requestId = 0;
|
||||
|
||||
this.runWorker();
|
||||
}
|
||||
|
||||
terminate() {
|
||||
if (this.worker) {
|
||||
this.worker.terminate();
|
||||
|
||||
for (const listener of this.listeners.values()) {
|
||||
listener({error: 'Worker terminated'});
|
||||
}
|
||||
}
|
||||
this.worker = null;
|
||||
}
|
||||
|
||||
runWorker() {
|
||||
const workerProc = () => {
|
||||
const { parentPort } = require('worker_threads');
|
||||
|
||||
const sleep = (ms) => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
if (parentPort) {
|
||||
parentPort.on('message', async(mes) => {
|
||||
let result = {};
|
||||
try {
|
||||
const fn = new Function(`'use strict'; return ${mes.fn}`)();
|
||||
result.result = await fn(mes.args, sleep);
|
||||
} catch (e) {
|
||||
result = {error: e.message};
|
||||
}
|
||||
|
||||
result.requestId = mes.requestId;
|
||||
parentPort.postMessage(result);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const worker = new Worker(`const wp = ${workerProc.toString()}; wp();`, {eval: true});
|
||||
|
||||
worker.on('message', (mes) => {
|
||||
const listener = this.listeners.get(mes.requestId);
|
||||
if (listener) {
|
||||
this.listeners.delete(mes.requestId);
|
||||
listener(mes);
|
||||
}
|
||||
});
|
||||
|
||||
worker.on('error', (err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
worker.on('exit', () => {
|
||||
this.terminate();
|
||||
});
|
||||
|
||||
this.worker = worker;
|
||||
}
|
||||
|
||||
//async
|
||||
run(params) {//args, fn
|
||||
return new Promise((resolve, reject) => {
|
||||
this.requestId++;
|
||||
|
||||
this.listeners.set(this.requestId, (mes) => {
|
||||
if (mes.error)
|
||||
reject(new Error(mes.error));
|
||||
else
|
||||
resolve(mes.result);
|
||||
});
|
||||
|
||||
if (this.worker) {
|
||||
this.worker.postMessage({requestId: this.requestId, args: params.args, fn: params.fn.toString()});
|
||||
} else {
|
||||
reject(new Error('Worker does not exist'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class HeavyCalc {
|
||||
constructor(opts = {}) {
|
||||
this.threads = opts.threads || 1;
|
||||
this.singleton = opts.singleton || false;
|
||||
this.terminated = false;
|
||||
|
||||
this.workers = [];
|
||||
this.load = [];
|
||||
for (let i = 0; i < this.threads; i++) {
|
||||
const worker = new CalcThread();
|
||||
this.workers.push(worker);
|
||||
this.load.push(0);
|
||||
}
|
||||
}
|
||||
|
||||
async run(params) {
|
||||
if (this.terminated || !this.workers.length)
|
||||
throw new Error('All workers terminated');
|
||||
|
||||
//находим поток с минимальной нагрузкой
|
||||
let found = 0;
|
||||
for (let i = 1; i < this.load.length; i++) {
|
||||
if (this.load[i] < this.load[found])
|
||||
found = i;
|
||||
}
|
||||
|
||||
try {
|
||||
this.load[found]++;
|
||||
return await this.workers[found].run(params);
|
||||
} finally {
|
||||
this.load[found]--;
|
||||
}
|
||||
}
|
||||
|
||||
terminate() {
|
||||
for (let i = 0; i < this.workers.length; i++) {
|
||||
this.workers[i].terminate();
|
||||
}
|
||||
this.workers = [];
|
||||
this.load = [];
|
||||
this.terminated = true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HeavyCalc;
|
||||
Reference in New Issue
Block a user