nodejs 使用 Worker、Web Worker使用


nodejs 使用 Worker、Web Worker 使用

前言

之前用 ffmpeg 跟 worker 实现批量合成视频时就有用到 worker 来实现,最近弄一个批量转音频格式又用到了 ffmpeg。然后发现不用 worker 根本行不通,电脑直接卡死。简单记录一下 worker 的用法。

使用步骤

主线程

  1. 通过创建一个 Worker 实例,参数是 worker 脚本的路径
  2. 通过worker.postMessage给 worker 线程传递数据
  3. 绑定message事件监听,监听到完成后通过worker.terminate()结束 worker 线程。

Worker 线程

  1. 引入parentPort,绑定message监听事件,参数就是主线程通过worker.postMessage传递的参数
  2. 任务执行完毕后,通过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 环境的类似,有三点区别:

  1. nodejs 绑定监听事件是.on('message', () => {})的形式绑定的,而浏览器则还是addEventListener或者onmessage那一套
  2. nodejs 监听message事件,参数就是传递的数据,而浏览器的参数是event对象,eventdata属性才是数据
  3. nodejsworkerjs是引入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);
});

文章作者: 赤蓝紫
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 赤蓝紫 !
评论
  目录