van*_*owm 3 javascript web-worker
我使用网络工作者来计算用户单击按钮时元素的位置。每次点击都会产生一个新的工作人员。一旦工作人员完成其任务,它就会终止。
我注意到,有时主进程收到的新生成的工作人员的响应会有延迟(有时是一整秒,当浏览器空闲一段时间时),所以我尝试将工作人员留在内存中并重新使用它们,只是如果所有人都很忙,就会产生新的。这似乎可以解决延迟问题(平均而言,通过这种方式,第一个响应的接收速度也快了 4-6 倍),但现在我想知道,从长远来看,让工作人员留在内存中是一个好主意,特别是因为在这种情况下例如每个消耗 1.5mb RAM?
for(let i = 0; i < 100; i++)
table.appendChild(document.createElement("div"));
const workerURL = URL.createObjectURL(new Blob(["(" + (() =>
{
const timeNow = performance.now();
// worker code
const pref = {};
this.onmessage = e => {
Object.assign(pref, e.data);
if (pref.status)
this.postMessage(performance.now() - pref.time);
}; //set preferences
(function loop(timestamp = performance.now())
{
setTimeout(loop);
if (!pref.status || timestamp - pref.time < pref.speed)
return;
pref.time = timestamp;
pref.position++;
this.postMessage(pref); //send new position
})();
}).toString() + ")()"]));
const animations = (() =>
{
const workers = new Map();
const showWorkers = (i=0) =>
{
while(workersList.children.length > workers.size)
workersList.removeChild(workersList.lastChild);
let busy = 0
for(let [_worker, pref] of workers)
{
const node = workersList.children[i++] || document.createElement("span");
node.textContent = pref.id;
node.classList.toggle("active", pref.status);
node.style.setProperty("--bg", "#" + pref.bg);
node.style.setProperty("--color", pref.color);
if (pref.status)
busy++;
if (!node.parentNode)
workersList.appendChild(node);
}
stats.textContent = "Workers: " + workers.size + " Busy: " + busy;
};
const initPref = pref =>
{
const defaultPref = {
bg: (~~(Math.random() * 16777215)).toString(16).padStart(6, 0),
speed: ~~(Math.random() * 50 + 10),
};
pref = Object.assign({},
defaultPref, //set default parameters
pref, //restore parameters from existing worker
persist.checked ? {} : defaultPref, //reset to default
{position: -1, status: 1, time: 0});//reset some of the parameters
pref.color = ["black","white"][~~([0,2,4].map(p=>parseInt(pref.bg.substr(p,2),16)).reduce((r,v,i)=>[.299,.587,.114][i]*v+r,0)<128)]; //luminiacity
return pref;
}
showWorkers();
let id = 1;
return {
start: () =>
{
const timeStart = performance.now();
let worker, pref = {}, prevNode;
//find idle worker
for(let [_worker, _pref] of workers)
{
if (!_pref.status)
{
worker = _worker;
pref = _pref;
break;
}
}
pref = initPref(pref);
if (!worker)
{
worker = new Worker(workerURL);
pref.id = id++
}
worker.onmessage = e =>
{
if (typeof e.data == "number")
{
return response.textContent = ("Worker " + pref.id + " responded in " + (performance.now() - timeStart).toFixed(1) + "ms\n" + response.textContent).split("\n").slice(0, 100).join("\n");
}
Object.assign(pref, e.data); //update pref
if (prevNode)
{
prevNode.removeAttribute("style");
delete prevNode.dataset.id;
}
if (pref.position >= table.children.length) //finish worker
{
if (reuseWorkers.checked)
{
pref.status = 0;
worker.postMessage(pref); //stop worker
}
else
{
worker.terminate();
workers.delete(worker);
}
showWorkers(); //update stats
return;
}
const node = table.children[pref.position];
node.style.setProperty("--bg", "#" + pref.bg);
node.style.setProperty("--color", pref.color);
node.dataset.id = pref.id;
prevNode = node;
};
worker.postMessage(pref);
workers.set(worker, pref);
showWorkers();
},
}
})();
start.onclick = () => animations.start();
Run Code Online (Sandbox Code Playgroud)
#table
{
display: inline-grid;
grid-template-columns: repeat(10, 10fr);
}
#table > *
{
outline: 1px solid black;
background-color: white;
width: 1em;
height: 1em;
}
#table > *::before
{
content: "";
}
#table > [style]::before
{
content: attr(data-id);
width: 125%;
height: 125%;
font-size: 0.7em;
line-height: 1.75em;
display: inline-flex;
justify-content: center;
position: relative;
background-color: var(--bg);
color: var(--color);
border: 1px solid;
top: -25%;
left: -25%;
border-radius: 1em;
}
.nowrap
{
white-space: nowrap;
display: flex;
align-items: flex-start;
}
#workersList
{
display: inline-flex;
flex-wrap: wrap;
align-content: flex-start;
margin-left: 0.5em;
}
#workersList > *
{
padding: 0.2em;
border: 1px solid black;
margin: -1px 0 0 -1px;
background-color: white;
min-width: 1em;
height: 1em;
text-align: center;
}
#workersList > .active
{
background-color: var(--bg);
color: var(--color);
border-radius: 100%;
}
#response
{
white-space: pre;
max-height: 10em;
overflow: auto;
vertical-align: top;
display: inline-block;
margin: 0 0.5em;
padding: 0 0.5em;
flex-shrink: 0;
}
#stats
{
margin-left: 1em;
}
Run Code Online (Sandbox Code Playgroud)
<div>
<button id="start">Start</button>
<label><input type="checkbox" id="reuseWorkers">Re-use idle workers</label>
<label><input type="checkbox" id="persist">Re-use color/speed</label>
<span id="stats"></span>
</div>
<div class="nowrap">
<div id="table"></div>
<div id="response"></div>
<div id="workersList"></div>
</div>
Run Code Online (Sandbox Code Playgroud)
PS 如果它闲置了一段时间,我可能可以添加自我终止。
是的,绝对应该重用你的工人。
启动一个新的 Worker 意味着必须生成一个新的进程、一个新的事件循环和一个新的 JS 上下文。正如规范所述,这是一项繁重的操作:
Workers(这些后台脚本在本文中被称为)相对较重,并且不适合大量使用。例如,为四兆像素图像的每个像素启动一名工作人员是不合适的。[...]
一般来说,worker 的寿命应该很长,启动性能成本很高,每个实例的内存成本也很高。
有一些积极的讨论(我参加了),以改进Worker
界面以防止网络作者做你所做的事情。目前,一次性 Workers 太诱人了,因为设置多任务 Workers 相对复杂。希望这次讨论能让 Workers 易于重用。
实际上,根据经验,您启动的 Workers 数量永远不应该超过navigator.hardwareConcurrency - 1
。如果不这样做,您的 Workers 的并发性将通过任务切换而不是真正的并行性来完成,这会导致显着的性能损失。
关于 Workers 的内存消耗,如果您正确地删除了对每个任务中使用的对象的引用,垃圾收集器通常会完成其工作,即使压力来自另一个线程。正如规范引用所暗示的那样,即使是空的 Worker 也有自己的内存占用,因此启动大量 Worker 实际上会增加更多的压力。
归档时间: |
|
查看次数: |
692 次 |
最近记录: |