实现带并发限制的异步调度器 Scheduler
题目
实现一个带并发限制的异步调度器 Scheduler
,保证同时运行的任务最多有N个。完善下面代码中的Scheduler
类,使得以下程序能正确输出:
js
class Scheduler {
add(promiseCreator) { ... }
// ...
}
const timeout = (time) => new Promise(resolve => {
setTimeout(resolve, time)
})
const scheduler = new Scheduler(n)
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order))
}
addTask(1000, '1') // 任务1
addTask(500, '2') // 任务2
addTask(300, '3') // 任务3
addTask(400, '4') // 任务4
// 打印顺序是:2 3 1 4
题目分析
假设N为2,也就是保证同时运行的任务有2个。那么在执行addTask
4步操作之后,整体的流程应该是这样的。
- 起始1、2两个任务开始执行;
- 500ms时,2任务执行完毕,输出2,任务3开始执行;
- 800ms时,3任务执行完毕,输出3,任务4开始执行;
- 1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行;
- 1200ms时,4任务执行完毕,输出4;
为什么会出现这样的结果?我们来具体分析一下
首先连续执行了4次addTask
,由于只能同时运行的任务有2个,所以,任务1和任务2将直接运行,任务1将在1000ms之后运行,任务2将在500ms之后运行,所以,任务2肯定会比任务1执行的快。当任务2执行完毕之后,输出2。紧接着执行任务3,此时任务1执行也就经过了500ms,还有500ms没有执行完,而任务3只需要300ms就执行完毕,所以任务3也会比任务1执行的快。又过了300ms(共计过了800ms)任务3执行完毕,输出3。任务4开始执行,任务4需要400ms执行完毕,而任务1目前只需要200ms,所以任务1会比任务4先执行,200ms之后(共计1000ms)任务1执行完毕,输出1,在过了200ms(共计1200ms),任务4执行完毕,输出4。
下面我们用图来表示一下
知道了这道题目具体要干啥了,下面就来看看代码是如何实现的
代码实现
直接上完整代码好了~
javascript
class Scheduler {
constructor(max) {
this.max = max;
this.count = 0; // 用来记录当前正在执行的异步函数
this.queue = new Array(); // 表示等待队列
}
async add(promiseCreator) {
/*
此时count已经满了,不能执行本次add需要阻塞在这里,将resolve放入队列中等待唤醒,
等到count<max时,从队列中取出执行resolve,执行,await执行完毕,本次add继续
*/
if (this.count >= this.max) {
await new Promise((resolve, reject) => this.queue.push(resolve));
}
this.count++;
let res = await promiseCreator();
this.count--;
if (this.queue.length) {
// 依次唤醒add
// 若队列中有值,将其resolve弹出,并执行
// 以便阻塞的任务,可以正常执行
this.queue.shift()();
}
return res;
}
}
const timeout = time =>
new Promise(resolve => {
setTimeout(resolve, time);
});
const scheduler = new Scheduler(2);
const addTask = (time, order) => {
//add返回一个promise,参数也是一个promise
scheduler.add(() => timeout(time)).then(() => console.log(order));
};
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
// output: 2 3 1 4
这块代码中我们主要加了add部分。
首先我们来分析一下 Scheduler
这个类。max
表示同时可以执行任务的最大数量。count
用来记录当前正在执行的异步函数。每次addTask
都会通过scheduler.add
添加一个异步任务。
进入add
函数中,首先需要做的事情是当前已经正在执行的任务有没有到达最大的任务数。
如果没有达到最大的任务数(比如刚开始的加入任务一和任务二,此时任务是空的),每次执行await promiseCreator();
这一步的时候,使用async
await
,当promiseCreator
没有执行完毕的时候,会阻塞后面的任务。所以当前两个任务被addTask
加入的时候,执行add
的时候,都会阻塞后面的任务。而我们的四个任务连续被加入的。当add
任务三和任务四的时候,发现此时count
已经满了,所以需要阻塞在这里,将resolve
放入队列中等待唤醒吗,具体什么时候被唤醒呢? ,当前面的任务有任何一个执行完毕之后,就可以被唤醒 。这里使用queue
来维护resolve
,add
任务三和任务四的时候,会先后给queue
推入这两个promise
的resolve
。
经过500ms ,任务二会先执行完毕,也就是await promiseCreator();
执行完毕之后,打印2,然后继续之后后续的代码,此时从queue
里面将第一个resolve
弹出,并执行。执行之后,任务3也就不再阻塞了,将继续执行await promiseCreator();
.
再经过300ms 任务三先执行完毕之后(任务还在继续执行中),打印3,然后继续之后后续的代码,此时从queue
里面将resolve弹出,并执行。执行之后,任务4也就不再阻塞了,将继续执行await promiseCreator();
.
再经过200ms 任务一终于执行完毕之后,打印1,然后继续之后后续的代码,此时queue
里面已经是空的了
再经过200ms任务四执行完毕,打印4
以上就是大致的全部流程
欢迎点赞,哪里有疑问的欢迎在评论区留言。