实现最大异步并发执行队列

题目说明

日常开发中遇到异步并发执行时我们通常会使用 Promise.All([]) ,但是如果这个并发数量很大(如超过100)那么我们考虑到服务器的并发压力就需要设置一个最大并发数。

这也是一个初/中级的热门面试题,本文就详细介绍如何用各种姿势来实现 最大异步并发执行队列

typescript 复制代码
/**
 * 最大异步并发执行队列
 * tasks 任务列表
 * maxConcurrency 最大并发数
 * @returns {Promise<void>}
 */
async function maxAsyncConcurrency(
  tasks: Array<() => Promise<void>>,
  maxConcurrency: number,
) {
  // 实现这个函数
  
}

测试代码

typescript 复制代码
const wait = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const waitLog = async (ms, text) => {
  await wait(ms);
  console.log(text);
};
const main = async () => {
  await maxAsyncConcurrencyRecursion(
    [
      () => waitLog(1000, 1),
      () => waitLog(1000, 2),
      () => waitLog(1000, 3),
      () => waitLog(1000, 4),
      () => waitLog(1000, 5),
      () => waitLog(1000, 6),
      () => waitLog(1000, 7),
      () => waitLog(1000, 8),
      () => waitLog(1000, 9),
    ],
    3,
  );
}
main();

思路1:递归实现(最好理解)

通过递归方式实现,把每个并发当成一个运行管道,每个管道实现为一个运行任务的异步函数,函数中完成一个任务就从队列里取下一个任务继续执行,直到清空队列即可。

typescript 复制代码
async function maxAsyncConcurrencyRecursion(tasks, maxConcurrency) {
  const queue = [...tasks];
  // 运行管道
  const pipeRunFn = async (fn) => {
    await fn();
    if (queue.length > 0) {
      const nextFn = queue.shift();
      await pipeRunFn(nextFn);
    }
  };
  // 最大运行管道
  const pipes = queue.splice(0, maxConcurrency);
  await Promise.all(pipes.map(pipeRunFn));
}

思路2:非递归实现

将思路1中的管道异步函数递归切换成 while 循环条件来实现。

typescript 复制代码
async function maxAsyncConcurrency(fns, max) {
  const queue = [...fns];
  let active = 0;
  while(queue.length) {
    if (active >= max) {
      await wait(100); // 如果并发已经达到最大,就等会再进入while循环继续轮询
      continue;
    }
    const fn = queue.shift();
    active++;
    fn().finally(() => {
      active--;
    });
  }
}

更加贴合实践的用法,面向对象象实现流式新增任务

题目

typescript 复制代码
class RequestQueue {
  private maxConcurrent: number; // 最大并发数量
  private queue: Array<() => Promise<void>> = []; // 存储任务队列
  
  constructor(maxConcurrent: number) {
    this.maxConcurrent = maxConcurrent;
  }

  /** 添加任务 */
  public addTask(task: () => Promise<void>) {}
}

测试代码

typescript 复制代码
const main = async () => {
  const reqQueue = new RequestQueue(3);
  reqQueue.addTask(() => waitLog(1000, 1))
  await wait(100);
  reqQueue.addTask(() => waitLog(1000, 2))
  await wait(100);
  reqQueue.addTask(() => waitLog(1000, 3))
  await wait(100);
  reqQueue.addTask(() => waitLog(1000, 4))
  await wait(2000);
  reqQueue.addTask(() => waitLog(1000, 5))
  await wait(100);
  reqQueue.addTask(() => waitLog(1000, 6))
  await wait(100);
  reqQueue.addTask(() => waitLog(1000, 7))
  await wait(100);
  reqQueue.addTask(() => waitLog(1000, 8))
  await wait(100);
  reqQueue.addTask(() => waitLog(1000, 9))
}
main();

递归实现

流式增加任务,而不是一开始就拿到全量的任务列表。新增任务时自动触发并发执行

typescript 复制代码
class RequestQueueRecursion {
  private maxConcurrent: number; // 最大并发数量
  private queue: Array<() => Promise<void>> = []; // 存储任务队列
  private active: number = 0; // 当前正在运行的任务计数

  constructor(maxConcurrent: number) {
    this.maxConcurrent = maxConcurrent;
  }

  /** 添加一个任务到队列中 */
  public addTask(task: () => Promise<void>) {
    this.queue.push(task);
    this.execute();
  }

  private async execute() {
    while(this.active < this.maxConcurrent && this.query.length > 0) {
      this.active ++;
      const fn = this.query.shift();
      fn().finally(() => {
        this.active--;
        this.execute();
      });
    }
  }
}

非递归实现

typescript 复制代码
class RequestQueue {
  private maxConcurrent: number; // 最大并发数量
  private queue: Array<() => Promise<any>> = []; // 存储任务队列
  private active: number = 0; // 当前正在运行的任务计数

  constructor(maxConcurrent: number) {
    this.maxConcurrent = maxConcurrent;
  }

  /** 添加一个任务到队列中 */
  public addTask(task: () => Promise<any>) {
    this.queue.push(task);
    this.execute();
  }

  /** 运行管道 */
  private async execute() {
    const queue = this.queue;
    while(queue.length > 0) {
      if (this.active >= this.maxConcurrent) {
        await wait(100);
        continue;
      }
      this.active ++;
      const fn = queue.shift();
      fn().finally(() => {
        this.active--;
      });
    }
  }
}
相关推荐
木心操作6 分钟前
nodejs动态创建sql server表
前端·javascript·sql
一个很帅的帅哥8 分钟前
Vue中的data为什么是函数?
前端·javascript·vue.js·data
南屿im34 分钟前
用 Node.js 开发命令行工具:打造你的高效 CLI
前端·javascript
一个很帅的帅哥3 小时前
Vue keep-alive
前端·javascript·vue.js·keep-alive
lbh3 小时前
Chrome DevTools 详解(一):Elements 面板
前端·javascript·浏览器
明里人3 小时前
React 状态库:Zustand 和 Jotai 怎么选?
前端·javascript·react.js
儒雅的烤地瓜4 小时前
JS | 如何把一个伪数组转换成一个真正的数组?
javascript·from方法·数组转换·扩展运算符·slice方法·push方法
β添砖java5 小时前
交互动效设计
前端·javascript·交互
用户56170657111476 小时前
scratch二次开发--如何在舞台区开启网络摄像头(Turbowarp版)
javascript
进阶的鱼6 小时前
React+ts+vite脚手架搭建(三)【状态管理篇】
前端·javascript·react.js