美文网首页
【面试题】如何在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