nodejs 使用 Worker、Web Worker 使用
前言
之前用 ffmpeg 跟 worker 实现批量合成视频时就有用到 worker 来实现,最近弄一个批量转音频格式又用到了 ffmpeg。然后发现不用 worker 根本行不通,电脑直接卡死。简单记录一下 worker 的用法。
使用步骤
主线程:
- 通过创建一个 Worker 实例,参数是 worker 脚本的路径
- 通过
worker.postMessage
给 worker 线程传递数据
- 绑定
message
事件监听,监听到完成后通过worker.terminate()
结束 worker 线程。
Worker 线程:
- 引入
parentPort
,绑定message
监听事件,参数就是主线程通过worker.postMessage
传递的参数
- 任务执行完毕后,通过
parentPort.postMessage()
给主线程传递信息。
实践
处理一千个数字相加,并且模拟每次相加都需要额外耗时。(_我的电脑太拉了,用公司的 mac 实际可以处理一万个数字,并且大概需要 10s_)
直接处理
首先,先不使用 Worker 多线程来看看效率如何。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const wait1ms = () => {
return new Promise((resolve) => {
setTimeout(() => resolve(), 1);
});
};
(async () => {
const numbers = Array(1000)
.fill(0)
.map((value, index) => index);
console.time();
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
await wait1ms();
sum += numbers[i];
}
console.log(sum);
console.timeEnd();
})();
|
差不多 15s 这样子
使用 Worker 多线程处理
把任务分给 32 个 worker 线程。注意,一般不会开启这么多 worker 线程,一般太多会浪费资源之类的。但是在这个 demo 中,使用 32 个线程效率很高,更能衬托出 worker 线程的强大就这么弄了。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| const { Worker } = require("worker_threads");
(async () => {
const numbers = Array(10000)
.fill("")
.map((value, index) => index);
console.time();
const workerNum = 7;
const count = Math.ceil(numbers.length / workerNum);
let sum = 0;
let completeWorkerNum = 0;
while (numbers.length > 0) {
const workerData = numbers.splice(0, count);
const worker = new Worker("./worker.js");
worker.postMessage(workerData);
worker.on("message", (msg) => {
sum += msg;
completeWorkerNum++;
if (completeWorkerNum === workerNum) {
console.log(sum);
console.timeEnd();
}
worker.terminate();
});
}
})();
|
worker.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const { parentPort } = require("worker_threads");
const wait1ms = () => {
return new Promise((resolve) => {
setTimeout(() => resolve(), 1);
});
};
parentPort.on("message", async (numbers) => {
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
await wait1ms();
sum += numbers[i];
}
parentPort.postMessage(sum);
});
|
😔。用自己的电脑真的跟用公司电脑跑差太多了。公司电脑处理一万条数据,一样 32 个 worker 线程,只需要几百毫秒。
Web Worker 使用
上面的 Worker 使用是在 nodejs 环境中运行的,实际上还可以在浏览器环境中使用 Web Worker。
使用方式跟 nodejs 环境的类似,有三点区别:
- nodejs 绑定监听事件是
.on('message', () => {})
的形式绑定的,而浏览器则还是addEventListener
或者onmessage
那一套
- nodejs 监听
message
事件,参数就是传递的数据,而浏览器的参数是event
对象,event
的data
属性才是数据
nodejs
的workerjs
是引入parentPort
进行事件的监听,而浏览器可以直接使用self
来进行监听
$\color{red}{Worker线程的全局对象是self
,而不是浏览器环境中的window
}$
代码:
html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
(async () => {
const numbers = Array(10000)
.fill("")
.map((value, index) => index);
console.time();
const workerNum = 7;
const count = Math.ceil(numbers.length / workerNum);
let sum = 0;
let completeWorkerNum = 0;
while (numbers.length > 0) {
const workerData = numbers.splice(0, count);
const worker = new Worker("./worker.js");
worker.postMessage(workerData);
worker.onmessage = (event) => {
sum += event.data;
completeWorkerNum++;
if (completeWorkerNum === workerNum) {
console.log(sum);
console.timeEnd();
}
worker.terminate();
};
}
})();
</script>
</body>
</html>
|
worker.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const wait1ms = () => {
return new Promise((resolve) => {
setTimeout(() => resolve(), 1);
});
};
self.addEventListener("message", async (event) => {
const numbers = event.data;
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
await wait1ms();
sum += numbers[i];
}
self.postMessage(sum);
});
|