持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的9天,点击查看活动详情
一、前情回顾
上文介绍 thread-loader 的入口方法 WorkerPool.prototype.run 方法的内部逻辑,重点介绍了 this.poolQueue.push 方法从接收数据(data, cb)一直到 neo-async 调用 worker 启动对 data 的处理的全过程。
这个部分涉及到 neo-async 中 queue 这个异步队列的逻辑,他是实现并发的核心,但有几点仍需要注意:
-
workerPool.run(data, cb)传递的第一个参数我们称之为data对象,第二个参数是回调,用于接收worker处理data后返回的loaders的执行结果的回调函数,称为cb; -
this.poolQueue是调用asyncQueue方法得到的异步队列,声明该队列时传入的worker是this.distribute方法; -
在
neo-async调用worker时, 即调用this.distriburte,worker接收的第二个参数不是前面的cb,而是neo-async内部的runQueue方法创造出来的done方法;
本篇小作文接上文讨论 wokerPool.run 在 push(data, cb) 后,neo-async 开始调用 woker 处理 data 的逻辑;
二、 this.distributeJob.bind(this)
通过前面的描述,我们得知 this.distributeJob 方法是 poolQueue 的 worker,也就是负责处理 push 到 poolQueue 的 data 的方法。distributeJob 方法是 WorkerPool 的原型方法;
export default class WorkerPool {
constructor(options) {
// ...
this.poolQueue = asyncQueue(
this.distributeJob.bind (this), // 创建 worker 并分配任务
options.poolParallelJobs // 并发数
);
// ....
}
// 这个方法就是上面构造函数中创建 poolQueue 时传递的 worker
distributeJob(data, callback) {}
}
2.1 this.distributeJob 方法细节
-
方法位置:
thread-loader/src/WorkerPool.js -> WorkerPool.prototype.distributeJob -
方法参数:
- 2.1
data: 需要被distribute方法处理的data对象。这个data就是前文workerPool.run(data, cb)传入的包含loaders的data对象; - 2.2
callback: 回调函数,当distributeJob完成其既有逻辑后需要调用的回调。前面强调过这个callback的作用是接收data处理后的结果的,所谓处理结果就是thread-loader执行完data中的loader后得到的结果。注意,这个callback不是workerPool.run(data, cb)中传入的cb,而是neo-async runQueue创建的done方法;
- 2.1
-
方法作用:
distributeJob顾名思义,分配任务。前面也提到过WorkerPool这个类就是负责调度 worker 的。具体逻辑如下:- 3.1 首先选取最合适的
worker,所谓最合适的标准是任务最少; - 3.2 判断找到的最合适的
worker任务数是否为0或者当前worker 池(this.workers)中worker数量是不是已经超过了numberOfWorkers的数量限制,如果超过了就用找到的工作数量最少的worker处理任务。 - 3.3 这里相当于是
this.workers中的worker数量尚未超出哦numberOfWorkers数量限制的情况,这个时候新建worker来运行任务;
- 3.1 首先选取最合适的
-
worker和WorkerPool实例的关系
这里先解析一个名词:worker;在 thread-loader 中到处都是 worker,这让个初看的人十分懵逼,另外我觉得这个命名方式也是扯淡极了,虽然作者设计的很🐂🍺,但是这个槽点也必须不能放过他。
第一个 worker 就是 this.poolQueue 的 worker,poolQueue 的 worker 说的是 this.distributeJob 这个方法。这个 worker 是处理 data 的一个方法。
另外还有一个东西也叫做 worker,下面讲 createWorker 的时候就会提到。在 createWorker 中创建的 WorkerPool 实例也叫做 worker,但是这个 worker 和上面的 poolQueue worker 不一样,这里的 worker 是个实实在在的 子进程。
worker 和 WorkerPool 实例的关系,类比 快递 和 快递员 的关系,而 WorkerPool 类则相当于快递公司;
worker 承载具体任务,而 workerPool 则负责调度管理这些 worker,就像网购买的东西发的是 快递包裹,而不是发一个 快递员,但是最后由 快递员 配送。具体逻辑如下:
export default class WorkerPool {
constructor(options) {
// ...
this.poolQueue = asyncQueue(
this.distributeJob.bind (this), // 创建 worker 并分配任务
options.poolParallelJobs // 并发数
);
// ....
}
distributeJob(data, callback) {
// 找到任务最少的 worker 并使用他
let bestWorker;
for (const worker of this.workers) {
if (!bestWorker || worker.activeJobs < bestWorker.activeJobs) {
bestWorker = worker;
}
}
if (
bestWorker &&
(bestWorker.activeJobs === 0 || this.workers.size >= this.numberOfWorkers)
) {
// bestWorker.activeJobs 是说当前 worker 没有任务
// this.workers.size > this.numberOfWorkers 时,
// 说明已有的 worker 已经超过 numberOfWorkers 的数量限制了,
// 不能再让后面的新建 workers 了,说明找遍了所有的 worker 它就是最好的了
bestWorker.run(data, callback);
return;
}
// 如果走到这里,说明一个 worker 也没有,
// 或者此时 workers 数量没有超出 this.numberOfWorkers 的限制
// 此时新建 worker 然后调用 worker 的 run 方法
const newWorker = this.createWorker();
// 得到新的 newWorker 后,调用 newWorker.run 传入 data
// 和 distributeJob 的 callback 回调 callback
// callback 是 runQueue 创造的 done 方法
newWorker.run(data, callback);
}
createWorker() {}
}
2.2 this.createWorker 方法
-
方法位置:
thread-loader/src/WorkerPool.js -> WorkerPool.prototype.createWorker -
方法参数:暂无
-
方法作用:创建一个
PoolWorker实例,注意看好了,是POOL-WORKER而不是WORKER-POOL,单次换了换位置。。。很让初看的人费解。另外下面也要称呼PoolWorker的实例为worker了,上面已经区分过了,这个worker是子进程。具体工作如下:- 3.1 传入配置
workerNodeArgs、parallelJobs创建PoolWorker实例,其中workerNodeArgs表示传递给子进程的进程参数(比如执行命令:$ node some-cmd --evn,--evn就叫做进程参数);this.onJobDone是worker完成任务时需要触发的回调,这个就好像你上完一天班后下班打卡一样。 - 3.2
parallelJobs这表示可以并发执行的任务数量限制 - 3.3 把新建的
PoolWorker实例放到this.workers池子中,方便找到任务数最少的worker,也方便统计现有worker数量,防止超出this.numberOfWorkers的数量限制;
- 3.1 传入配置
export default class WorkerPool {
constructor(options) {}
distributeJob(data, callback) {
const newWorker = this.createWorker();
newWorker.run(data, callback);
}
createWorker() {
// 创建一个新 worker 实例
const newWorker = new PoolWorker(
{
nodeArgs: this.workerNodeArgs,
parallelJobs: this.workerParallelJobs,
},
() => this.onJobDone() // worker 的任务完成时要触发的回调
);
// 把 worker 实例放到 worker 池子中
this.workers.add(newWorker);
// 返回新建 worker
return newWorker;
}
onJobDone() { }
}
三、总结
本篇小作文讨论了 workerPool.poolQueue 的 worker 方法, WorkerPool.distributeJob 方法的详细逻辑:
- 找到最合适的
worker(子进程),标准是活跃任务数最少的; - 如果没有超过
this.numberOfWorkers的限制就新建worker,worker是PoolWorker的实例,可以理解成子进程;
受限于篇幅,下一篇我们详细讲解 PoolWorker 这个类是如何创建子进程以及父子进程间的通信。