piscina 库调研,用于 thread 预热

piscina 库是一个 Node.js Worker Thread Pool,进行了一些调研。

一些特性

  • piscina 会优先使用当前负载最小的实例,所以如果有 10 个零负载的 thread,那么一下调用 10 次 runTask 可以保证每个 thread 都接收到一次。

  • piscina 支持 thread 的异步初始化,使用方式:

    1
    2
    3
    4
    5
    6
    async function initialize() {
    await someAsyncInitializationActivity();
    return ({ a, b }) => a + b;
    }

    module.exports = initialize();
  • 执行 new Piscina() 时默认不会创建 thread,会按需在调用 runTask 时创建 thread。但可以通过设置 minThreads 来使得 new Piscina() 时就创建一定数量的 thread。

运行中 thread 创建

调用 runTask 时,遇到以下两种情况,会尝试创建新的 thread:

  1. 等待队列中有正在等待的任务(表示所有 thread 都在忙碌),并且当前 thread 数量没有超过 maxThreads。
  2. 没找到可用的 thread,并且当前 thread 数量没有超过 maxThreads。

创建过程中的 thread 是不可用的,会 piscina 会等待这个 thread 异步初始化,初始化完成之后才进行使用:

1
2
3
4
5
6
7
8
9
10
11
12
worker.on('message', (message : ReadyMessage) => {
if (message.ready === true) {
if (workerInfo.currentUsage() === 0) {
workerInfo.unref();
}

if (!workerInfo.isReady()) {
workerInfo.markAsReady();
}
return;
}
});

调度方式

寻找当前负载最小的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
findAvailable () : T | null {
let minUsage = this.maximumUsage;
let candidate = null;
for (const item of this.readyItems) {
const usage = item.currentUsage();
if (usage === 0) return item;
if (usage < minUsage) {
candidate = item;
minUsage = usage;
}
}
return candidate;
}

预热 demo

我写了一个预热 piscina 的 demo:https://github.com/meixg/piscina-warmup-demo。

预热前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
threadId: 1 done: 0th at 20.103199999779463
threadId: 2 done: 0th at 26.904699999839067
threadId: 3 done: 0th at 26.526700001209974
threadId: 5 done: 0th at 29.538399998098612
threadId: 4 done: 0th at 28.85249999910593
threadId: 6 done: 0th at 28.910900000482798
threadId: 3 done: 100th at 0.0568000003695488
threadId: 7 done: 0th at 13.932300001382828
threadId: 8 done: 0th at 22.18859999999404
threadId: 9 done: 0th at 10.147300001233816
threadId: 10 done: 0th at 15.620099999010563
threadId: 1 done: 100th at 0.08020000159740448
threadId: 2 done: 100th at 0.10360000282526016
threadId: 5 done: 100th at 0.05690000206232071
threadId: 4 done: 100th at 0.05640000104904175
threadId: 6 done: 100th at 0.06940000131726265
threadId: 7 done: 100th at 0.10370000079274178
threadId: 8 done: 100th at 0.0575999990105629
threadId: 9 done: 100th at 0.056199997663497925
threadId: 10 done: 100th at 0.07080000266432762

预热后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
threadId: 1 done: 0th at 1.8744000010192394
threadId: 2 done: 0th at 2.410700000822544
threadId: 3 done: 0th at 2.3161999993026257
threadId: 4 done: 0th at 2.9968000017106533
threadId: 5 done: 0th at 2.564599998295307
threadId: 6 done: 0th at 3.0161999985575676
threadId: 7 done: 0th at 3.555900000035763
threadId: 8 done: 0th at 3.5732999965548515
threadId: 9 done: 0th at 3.471400000154972
threadId: 10 done: 0th at 3.1979999989271164
threadId: 3 done: 100th at 0.057099997997283936
threadId: 5 done: 100th at 0.05829999968409538
threadId: 6 done: 100th at 0.11400000005960464
threadId: 7 done: 100th at 0.05640000104904175
threadId: 8 done: 100th at 0.08650000020861626
threadId: 9 done: 100th at 0.06109999865293503
threadId: 10 done: 100th at 0.058400001376867294
threadId: 1 done: 100th at 0.06509999930858612
threadId: 2 done: 100th at 0.055799998342990875
threadId: 4 done: 100th at 0.06909999996423721