尝试把 Node.js 同步计算转为异步方法

Nodejs 一般都碰到把异步方法写成同步的样子,很少有人会想把同步方法写成异步。今天你就看到了。

事情是这样子的,之前写的哪个异或运行,我写成了一个服务了。本来 Nodejs 就对计算密集不擅长,现在运算还是同步的,要是请求多了,这服务就慢了。这怎么能忍呢?

把异或运算写成长得像异步方法(为什么说是长得像,下面你就知道了),其实有个很常见的办法,那就是setTimeout,但是这次我选择process.nextTick,要问我为什么选这个,我只能说我就是想玩点没玩过的东西。实例如下:

1
2
3
4
5
6
function foo() {
console.error('foo');
}

process.nextTick(foo);
console.error('bar');

那要是函数有参数的呢?

1
2
3
4
5
6
function foo(_, vlaue) {
console.error("foo:", vlaue);
}

process.nextTick(foo, 1, 2);
console.error("bar");

或者更 hack 一点的写法

1
2
3
4
5
6
7
8
function foo(_, vlaue) {
return () => {
console.error("foo:", vlaue);
};
}

process.nextTick(foo(1, 2));
console.error("bar");

这个 tick 涉及到事件轮训,深一点的一时半会我说不明白。这里引用 nodejs 中文网 的的话

每当事件循环进行一次完整的行程时,我们都将其称为一个 tick。

当将一个函数传给 process.nextTick() 时,则指示引擎在当前操作结束(在下一个事件循环滴答开始之前)时调用此函数

虽然我们看到上面的例子是先打印bar后打印的foo,但是真的是利用到了多核吗?做个实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo(start, end) {
return () => {
let result = start;
for (let index = start + 1; index <= end; index++) {
result += index;
};
console.log(result);
};
}

foo(1, 3)();
process.nextTick(foo(1, 200));
process.nextTick(foo(1, 2));
console.log("bar");

然后运行

1
time node test.js

结果输出如下

1
2
3
4
5
6
bar
20100
3
node test.js 0.05s user 0.00s system 102% cpu 0.050 total

emmmm,怎么像是顺序执行的?这异步是假的?在网上找到一篇译文:理解 Node.js 里的 process.nextTick(),里面有这么一个描述

当然,我们无法通过 process.nextTick() 来获得多 CPU 下并行执行的真正好处,这只是模拟同一个应用在 CPU 上分段执行而已。

所以process.nextTick()根本利用不了多核,那怎么办?我又不想去碰 C++ addons(得承认是自己太菜写不了 C++)

其实还是有办法的,比如使用clusterChild process,Worker threads,这里就使用cluster写个测试:

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
function foo(start, end) {
let result = start;
for (let index = start + 1; index <= end; index++) {
result += index;
};
return result;
}

/**
* - 加载 clustr 模块
* - 设定启动进程数为 cpu 个数
*/
const cluster = require("cluster");
const numCPUs = require("os").cpus().length;

// 素数的计算
const min = 0;
const max = 1e8; // = 10000000
let primes = 0;

if (cluster.isMaster) {
const range = Math.ceil((max - min) / numCPUs);
let start = min;

for (let i = 0; i < numCPUs; i++) {
const worker = cluster.fork(); // 启动子进程
// 在主进程中,这会发送消息给特定的工作进程
worker.send({ start: start, range: range });

start += range;

worker.on("message", msg => {
primes += msg.data;
worker.kill();
});
}
// 当任何一个工作进程关闭的时候,cluster 模块都将会触发 'exit' 事件
cluster.on("exit", function(worker, code, signal) {
// console.log("worker " + worker.process.pid + " died");
});
process.on("exit", () => {
console.log("primes " + primes);
});
} else {
// 监听子进程发送的信息
process.on("message", msg => {
// console.log(msg);
const { start, range } = msg;
const data = foo(start + 1, start + range);
// 在工作进程中,这会发送消息给主进程
process.send({ data: data });
});
}
// console.log(foo(min, max));
// function test() {
// const range = Math.ceil((max - min) / numCPUs);
// let start = min;
// for (let i = 0; i < numCPUs; i++) {
// start += range;
// primes += foo(start + 1, start + range);
// }
// return primes;
// }
// console.log(test());

多核是用上了,是我例子写的太简单了吗?耗时还不如单核?下次研究。


尝试把 Node.js 同步计算转为异步方法
https://bubao.github.io/posts/9593efd8.html
作者
一念
发布于
2021年3月18日
许可协议