美文网首页
【面试题】如何在JS中控制任务并发数?

【面试题】如何在JS中控制任务并发数?

作者: ZT_Story | 来源:发表于2023-03-09 15:48 被阅读0次

前言

最近看到很多面经分享中都出现过这么一道题目

实现一个类或函数,使其支持最大并发数为2

这个问题看上去非常实用啊,如果说大型应用中需要批量发起大量的请求,浏览器单域名http1.1 以下最多只能共存6条TCP连接,有可能会阻塞很多重要的任务,比如下载或者上传,你需要控制并发数量,就需要自行实现一个任务调度器

思考

  1. 实现这么一个东西肯定是需要一个任务队列的
  2. 这个队列只能同时取出n个任务来执行
  3. 当任务结束后立即取下一个任务开始执行,直到任务队列为空

实现

按照上面的思路,我们先设计这个对象

class TaskScheduler {
    constructor(concurrentCount = 2) {
        // 并发上限
        this.concurrentCount = concurrentCount;
        // 运行中的任务数
        this.runningTaskCount = 0;
        // 任务列表
        this.tasks = [];
    }
    
    addTask(task) {
        return new Promise((resolve, reject) => {
            // 添加任务到队列里
            this.tasks.push({
                task,
                resolve,
                reject,
            });
        });
    }
}

到这里,大家可能会发现,任务加进去了,怎么执行呢?

添加的方法只管添加任务,我们还需要一个 run 方法来做任务的调度

class TaskScheduler {
    constructor(concurrentCount = 2) {
        // 并发上限
        this.concurrentCount = concurrentCount;
        // 运行中的任务数
        this.runningTaskCount = 0;
        // 任务列表
        this.tasks = [];
    }
    
    addTask(task) {
        return new Promise((resolve, reject) => {
            // 添加任务到队列里
            this.tasks.push({
                task,
                resolve,
                reject,
            });
            // 添加任务之后立即进入执行
            this._run();
        });
    }
    
    _run() {
        // 当任务列表不为空 且 正在运行的任务不超过并发上限 则继续执行下一个任务
        while (this.tasks.length > 0 && this.runningTaskCount < this.concurrentCount) {
            // 队列拿最新的任务
            const { task, resolve, reject } = this.tasks.shift();
            
            // 运行计数 + 1
            this.runningTaskCount++;
            
            // 运行任务
            const res = task();
            
            // 判断任务是异步还是同步任务
            if (res instanceof Promise) {
                res.then(resolve, reject).finally(() => {
                    // 执行结束 运行计数 - 1
                    this.runningTaskCount--;
                    // 递归调用 run 执行下一个任务
                    this._run();
                });
            } else {
                // 执行结束 运行计数 - 1
                this.runningTaskCount--;
                // 递归调用 run 执行下一个任务
                this._run();
            }
        }
    }

到这里,一个简单的并发任务调度器已经实现了,其实代码并不多,而且也不是很复杂,基本是围绕上面的几个条件来实现的

这里可能会有小伙伴纠结 _run() 函数中为什么需要用 while 是不是用 if 可以达到同样的效果?

的确,如果任务都是由 addTask 来进行添加的话,的确不需要用到 while,因为一添加就直接执行了,而且任务结束也会递归调用

但如果一开始就有一个初始化的任务列表的话,那么 if 就不够用了,就需要用到 while 了,代码如下

class TaskScheduler {
    constructor(concurrentCount = 2, tasks = []) {
        ...
        // 任务列表
        this.tasks = tasks;
    }
    
    addTask(task) {
        ...
    }
    
    _run() {
        ...
    }

不然就只能等第一个结束之后才能执行下一个任务,就变成并发只有 1 的调度器了

这里提供一个测试用例供大家测试:
GitHub 代码完整版+测试用例

相关文章

网友评论

      本文标题:【面试题】如何在JS中控制任务并发数?

      本文链接:https://www.haomeiwen.com/subject/yjoaldtx.html