实现带并发限制的异步调度器 Scheduler

实现带并发限制的异步调度器 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. 起始1、2两个任务开始执行;
  2. 500ms时,2任务执行完毕,输出2,任务3开始执行;
  3. 800ms时,3任务执行完毕,输出3,任务4开始执行;
  4. 1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行;
  5. 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来维护resolveadd任务三和任务四的时候,会先后给queue推入这两个promiseresolve

经过500ms ,任务二会先执行完毕,也就是await promiseCreator();执行完毕之后,打印2,然后继续之后后续的代码,此时从queue里面将第一个resolve弹出,并执行。执行之后,任务3也就不再阻塞了,将继续执行await promiseCreator();.

再经过300ms 任务三先执行完毕之后(任务还在继续执行中),打印3,然后继续之后后续的代码,此时从queue里面将resolve弹出,并执行。执行之后,任务4也就不再阻塞了,将继续执行await promiseCreator();.

再经过200ms 任务一终于执行完毕之后,打印1,然后继续之后后续的代码,此时queue里面已经是空的了

再经过200ms任务四执行完毕,打印4

以上就是大致的全部流程

欢迎点赞,哪里有疑问的欢迎在评论区留言。

相关推荐
运维阿江2 分钟前
【小白学HTML5】盒模型_第一讲
前端·html·html5
码界领航14 分钟前
【2025最新版】Chrome谷歌浏览器如何能恢复到之前的旧版本
前端·chrome
乐多_L1 小时前
使用vue3框架vue-next-admin导出表格excel(带图片)
前端·javascript·vue.js
南望无一1 小时前
React Native 0.70.x如何从本地安卓源码(ReactAndroid)构建
前端·react native
Mike_188702783511 小时前
1688代采下单API接口使用指南:实现商品采集与自动化下单
前端·python·自动化
鲨鱼辣椒️面1 小时前
HTML视口动画
前端·html
一小路一1 小时前
Go Web 开发基础:从入门到实战
服务器·前端·后端·面试·golang
堇舟1 小时前
HTML第一节
前端·html
纯粹要努力2 小时前
前端跨域问题及解决方案
前端·javascript·面试
小刘不知道叫啥2 小时前
React源码揭秘 | 启动入口
前端·react.js·前端框架