字节面试题:实现任务调度器(Scheduler)

突然想起去年面试字节时的一道题,题面大概是这样:

  1. 实现一个任务调度器 scheduler
  2. 只能同时执行 n 个任务
  3. 当有 n 个任务正在执行时,后安排的任务要排队

当时很久没面试了,紧张到冒汗😓,只写出一半就被叫停了;

今天辗转反侧睡不着,突然想起这道题,就给他写了吧 :)

实现

大致思路是:

  1. 定义 Scheduler 类:指定同时执行的任务数 n
  2. 有 schedule 方法:安排一个任务(task),如果执行中的达到 n 则入队,否则直接执行
  3. 包装器 wrapper:包装 task,使执行 task 完毕后检查是否要出队,即执行新任务

伪码如下:

typescript 复制代码
class Scheduler {
  // 计数当前执行中的数量
  private runningTask: number
  // 最大并发
  private capacity: number
  // 等待队列
  private pendingQueue: (() => Promise<any>)[]

  constructor(capacity: number) {
    this.capacity = capacity
    this.runningTask = 0
    this.pendingQueue = []
  }

  private wrapTask<T>(task: () => Promise<T>): () => Promise<void> {
    return async () => {
      // 执行 task
      // 判断 pending 队列是否空
      // 否,出队下一个 task
      // 是,更新计数
    }
  }

  public schedule<T>(task: () => Promise<T>): void {
    // 包装 task,得到 wrappedTask
    // 判断计数是否达到 n
    // 否,则直接执行 wrappedTask
    // 是,则入队
  }
}

然后,让我们来实现它:

typescript 复制代码
class Scheduler {
  // 计数当前执行中的数量
  private runningTask: number
  // 最大并发
  private capacity: number
  // 等待队列
  private pendingQueue: (() => Promise<any>)[]

  constructor(capacity: number) {
    this.capacity = capacity
    this.runningTask = 0
    this.pendingQueue = []
  }

  private wrapTask<T>(task: () => Promise<T>): () => Promise<void> {
    return async () => {
      // 执行 task
      await task()
      // 判断 pending 队列是否空
      if (this.pendingQueue.length > 0) {
        // 否,出队下一个 task
        const nextWrappedTask = this.pendingQueue.shift()!
        nextWrappedTask()
      } else {
        // 是,更新计数
        this.runningTask--
      }
    }
  }

  public schedule<T>(task: () => Promise<T>): void {
    // 包装 task,得到 wrappedTask
    const wrappedTask = this.wrapTask(task)
    // 判断计数是否达到 n
    if (this.runningTask < this.capacity) {
      // 否,则直接执行 wrappedTask
      wrappedTask()
    } else {
      // 是,则入队
      this.pendingQueue.push(wrappedTask)
    }
  }
}

进阶

这里还可以进一步:要求 schedule 返回 Task 的执行结果,怎么写?

EZ,我们只要:让 schedule 返回一个 Promise,并在 wrappedTask 中 resolve 结果 就行了,代码如下:

PS: 这里总结一个小技巧"传递 resolve/reject 来实现异步返回值"

typescript 复制代码
class Scheduler {
  private runningTask: number
  private capacity: number
  private pendingQueue: (() => Promise<any>)[]

  constructor(capacity: number) {
    this.capacity = capacity
    this.runningTask = 0
    this.pendingQueue = []
  }

  private wrapTask<T>(task: () => Promise<T>, resolve: (value: T) => void, reject: (error: any) => void): () => Promise<void> {
    return async () => {
      try {
        const res = await task()
        resolve(res)
        const nextTask = this.pendingQueue.shift()
        if (nextTask) {
          nextTask()
        } else {
          this.runningTask--
        }
      } catch (e) {
        reject(e)
      }
    }
  }

  public async schedule<T>(task: () => Promise<T>): Promise<T> {
    return (new Promise<T>((resolve, reject) => {
      const wrappedTask = this.wrapTask(task, resolve, reject)
      if (this.runningTask < this.capacity) {
        this.runningTask++
        wrappedTask()
      } else {
        this.pendingQueue.push(wrappedTask)
      }
    }))
  }
}
相关推荐
IT规划师1 小时前
编程术语中的同步与异步:从"等不等"到工程取舍
后端·面试
螺丝钉tina3 小时前
[Playwright ][16]Playwright + TypeScript API 自动化测试实战:API测试
前端·javascript·typescript
考研保研资料分享6 小时前
2022环境工程保研北师大:三校专业追问、论文贡献与择校复盘
面试
华玥作者8 小时前
Vue 3 + TypeScript + uni-app 组件库推荐:hy-app(华玥组件库)
vue.js·typescript·uni-app
Rain的Java大神之路9 小时前
面试官让你把 LLM、RAG、Agent、MCP 串起来讲清楚,90% 的 Java 工程师当场露馅
java·后端·面试
狂师10 小时前
我开发了一款免费开源的测试开发刷题学习网站
面试·开源·测试
利威尔xu21 小时前
Go IM 源码实战(14):本地缓存快 3000 倍,失效却难 10 倍
后端·缓存·微服务·面试·golang
利威尔xu1 天前
Go IM 源码实战(13):30 个会话只有 2 个红点,未读数错在三条路径
redis·后端·微服务·面试·golang
Momo__1 天前
你写的 import 重试,从来没生效过
前端·javascript·typescript