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

 更新时间:2024年03月26日 09:09:59   作者:翰玥  
这篇文章主要为大家详细介绍了如何基于JS实现一个带并发限制的异步调度器?Scheduler,保证同时运行的任务最多有N个,感兴趣的小伙伴可以了解下
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

题目

实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有N个。完善下面代码中的Scheduler类,使得以下程序能正确输出:

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。

下面我们用图来表示一下

知道了这道题目具体要干啥了,下面就来看看代码是如何实现的

代码实现

直接上完整代码好了~

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

到此这篇关于JavaScript实现带并发限制的异步调度器的文章就介绍到这了,更多相关JavaScript异步调度器内容请搜索程序员之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持程序员之家!

相关文章

最新评论

?


http://www.vxiaotou.com