字节面试官:请写一个异步并发调度器

2,127 阅读2分钟

关于异步并发调度器关键实现:

  • 需要有一个数组 queue,模拟队列(先进先出),依次进行异步请求处理。
  • add 方法用来添加异步请求;根据当前正在 running 的个数,判断添加异步请求是执行 run 还是将其放入到队列中。
  • run 方法真正执行异步请求
class Scheduler{
  constructor(max){
    this.max = max
    this.queue = []
    this.running = 0
  }
  add(task){
    return new Promise(resolve=>{
      // 关键,加了一层 Promise 的封装,将其 resolve 的函数交出去
      task.resolve = resolve
      if(this.running<this.max){
        this.run(task)
      }else{
        this.queue.push(task)
      }
    })
  }
  async run(task){
    if(task && typeof task === 'function'){
      this.running++
      await task()
      task.resolve()
      this.running--
      const fn = this.queue.shift()
      this.run(fn)
    }
  }
}

题目一

实现了异步并发器,自然我们可以解决下面的笔试题目:

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

class Scheduler {
    constructor() {
        this.count = 2
        this.queue = []
        this.run = []
    }

    add(task) {
             // ...
    }
}


const timeout = (time) => new Promise(resolve => {
    setTimeout(resolve, time)
})

const scheduler = new Scheduler()
const addTask = (time, order) => {
    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

// 一开始,1、2两个任务进入队列
// 500ms时,2完成,输出2,任务3进队
// 800ms时,3完成,输出3,任务4进队
// 1000ms时,1完成,输出1
// 1200ms时,4完成,输出4

题目二

狡猾的面试官还会对上面的题目进行变种,多一个 start 函数控制什么时候开始触发请求,题目如下:

//支持并发的调度器, 最多允许2两任务进行处理
const scheduler = new Scheduler(2)
scheduler.addTask(1, '1');   // 1s后输出’1'
scheduler.addTask(2, '2');  // 2s后输出’2'
scheduler.addTask(1, '3');  // 2s后输出’3'
scheduler.addTask(1, '4');  // 3s后输出’4'
scheduler.start();

分析得知:addTask 只是将异步请求放入到队列中,只有 start 后才会触发请求(或异步操作)。

  • 需要一个数组 queue 模拟队列,存放异步请求
  • addTask 将异步请求放入队列中
  • start 触发开始请求
  • run 方法真正执行异步请求
class Scheduler{
  constructor(max){
    this.max = max
    this.queue = []
  }

  addTask(duration, data){
    const p = () => new Promise((resolve)=>{
      setTimeout(()=>{
        console.log(data, dayjs().format('HH:MM:ss'))
        resolve()
      }, duration*1000)
    })

    this.queue.push(p)
  }

  async run(task){
    if(task && typeof task==='function'){
      await task()
      const fn = this.queue.shift()
      this.run(fn)
    }
  }
  
  start(){
    // max 个通道开启
    for(let i=0;i<this.max;i++){
      const fn = this.queue.shift()
      this.run(fn)
    }
  }
}