Promise面试题详解之控制并发


Posted in 面试题 onMay 14, 2021

前言

在写这篇文章的时候我有点犹豫,因为先前写过一篇类似的,一道关于并发控制的面试题,只不过那篇文章只给出了一种解决方案,后来在网上又陆续找到两种解决方案,说来惭愧,研究问题总是浅尝辄止,所以今天便放在一起,借着这道面试题再重新梳理一下。

题目是这样的:

有 8 个图片资源的 url,已经存储在数组 urls 中(即urls = [‘http://example.com/1.jpg', …., ‘http://example.com/8.jpg']),而且已经有一个函数 function loadImg,输入一个 url 链接,返回一个 Promise,该 Promise 在图片下载完成的时候 resolve,下载失败则 reject。

但是我们要求,任意时刻,同时下载的链接数量不可以超过 3 个。

请写一段代码实现这个需求,要求尽可能快速地将所有图片下载完成。

已有代码如下:

var urls = [
'https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg', 
'https://www.kkkk1000.com/images/getImgData/gray.gif', 
'https://www.kkkk1000.com/images/getImgData/Particle.gif', 
'https://www.kkkk1000.com/images/getImgData/arithmetic.png', 
'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif', 
'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg', 
'https://www.kkkk1000.com/images/getImgData/arithmetic.gif', 
'https://www.kkkk1000.com/images/wxQrCode2.png'
];
function loadImg(url) {
    return new Promise((resolve, reject) => {
        const img = new Image()
        img.onload = function () {
            console.log('一张图片加载完成');
            resolve();
        }
        img.onerror = reject
        img.src = url
    })
};

看到这个题目的时候,脑袋里瞬间想到了高效率排队买地铁票的情景,那个情景类似下图:

Promise面试题详解之控制并发

上图这样的排队和并发请求的场景基本类似,窗口只有三个,人超过三个之后,后面的人只能排队了。

首先想到的便是利用递归来做,就如这篇文章采取的措施一样,代码如下:

//省略代码

var count = 0;
//对加载图片的函数做处理,计数器叠加计数
function bao(){
    count++;
    console.log("并发数:",count)
    //条件判断,urls长度大于0继续,小于等于零说明图片加载完成
    if(urls.length>0&&count<=3){
    //shift从数组中取出连接
        loadImg(urls.shift()).then(()=>{
        //计数器递减
            count--
            //递归调用
            }).then(bao)
    }
}
function async1(){
//循环开启三次
    for(var i=0;i<3;i++){
        bao();
    }
}
async1()

以上是最常规的思路,我将加载图片的函数loadImg封装在bao函数内,根据条件判断,是否发送请求,请求完成后继续递归调用。

以上代码所有逻辑都写在了同一个函数中然后递归调用,可以优化一下,代码如下:

var count = 0;

// 封装请求的异步函数,增加计数器功能
function request(){
    count++;
    loadImg(urls.shift()).then(()=>{
            count--
            }).then(diaodu)

    
}
// 负责调度的函数
function diaodu(){
    if(urls.length>0&&count<=3){
        request();
    }
}

function async1(){
    for(var i=0;i<3;i++){
        request();
    }
}
async1()

上面代码将一个递归函数拆分成两个,一个函数只负责计数和发送请求,另外一个负责调度。

这里的请求既然已经被封装成了Promise,那么我们用Promise和saync、await来完成一下,代码如下:

//省略代码

// 计数器
var count = 0;
// 全局锁
var lock = [];
var l = urls.length;
async function bao(){
    if(count>=3){
        //超过限制利用await和promise进行阻塞;
        let _resolve;
        await new Promise((resolve,reject)=>{
            _resolve=resolve;
            // resolve不执行,将其推入lock数组;
            lock.push(_resolve);
        });
    }
    if(urls.length>0){
        console.log(count);
        count++
        await loadImg(urls.shift());
        count--;
        lock.length&&lock.shift()()
    }
}
for (let i = 0; i < l; i++) {
    bao();
}

大致思路是,遍历执行urls.length长度的请求,但是当请求并发数大于限制时,超过的请求用await结合promise将其阻塞,并且将resolve填充到lock数组中,继续执行,并发过程中有图片加载完成后,从lock中推出一项resolve执行,lock相当于一个叫号机;

以上代码可以优化为:

// 计数器
var count = 0;
// 全局锁
var lock = [];
var l = urls.length;
// 阻塞函数
function block(){
    let _resolve;
    return  new Promise((resolve,reject)=>{
        _resolve=resolve;
        // resolve不执行,将其推入lock数组;
        lock.push(_resolve);
    });
}
// 叫号机
function next(){
    lock.length&&lock.shift()()
}
async function bao(){
    if(count>=3){
        //超过限制利用await和promise进行阻塞;
        await block();
    }
    if(urls.length>0){
        console.log(count);
        count++
        await loadImg(urls.shift());
        count--;
        next()
    }
}
for (let i = 0; i < l; i++) {
    bao();
}

最后一种方案,也是我十分喜欢的,思考好久才明白,大概思路如下:

用 Promise.race来实现,先并发请求3个图片资源,这样可以得到 3 个 Promise实例,组成一个数组promises ,然后不断的调用 Promise.race 来返回最快改变状态的 Promise,然后从数组(promises )中删掉这个 Promise 对象实例,再加入一个新的 Promise实例,直到全部的 url 被取完。

代码如下:

//省略代码
function limitLoad(urls, handler, limit) {
    // 对数组做一个拷贝
    const sequence = [].concat(urls)
    let promises = [];

    //并发请求到最大数
    promises = sequence.splice(0, limit).map((url, index) => {
        // 这里返回的 index 是任务在 promises 的脚标,
        //用于在 Promise.race 之后找到完成的任务脚标
        return handler(url).then(() => {
            return index
        });
    });

    (async function loop() {
        let p = Promise.race(promises);
        for (let i = 0; i < sequence.length; i++) {
            p = p.then((res) => {
                promises[res] = handler(sequence[i]).then(() => {
                    return res
                });
                return Promise.race(promises)
            })
        }
    })()
}
limitLoad(urls, loadImg, 3)

第三种方案的巧妙之处,在于使用了Promise.race。并且在循环时用then链串起了执行顺序。

以上便是关于并发控制的一点点思考,有使用promise的,有不使用promise的,关键在于灵活运用,通过这次梳理,你有哪些思考呢

总结

到此这篇关于Promise面试题详解之控制并发的文章就介绍到这了,更多相关Promise控制并发内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!


Tags in this post...

面试题 相关文章推荐
String这个类型的class为何定义成final?
Nov 13 面试题
请编写一个 C 函数,该函数在给定的内存区域搜索给定的字符,并返回该字符所在位置索引值
Sep 15 面试题
为什么如下的代码int a=100,b=100;long int c=a * b;不能工作
Nov 29 面试题
为什么要有struct关键字
May 08 面试题
将"引用"作为函数返回值类型的格式、好处和需要遵守的规则
Feb 09 面试题
经典c++面试题五
Dec 17 面试题
密封类可以有虚函数吗
Aug 11 面试题
通用C#笔试题附答案
Nov 26 面试题
介绍一下write命令
Aug 10 面试题
几个Shell Script面试题
Aug 31 面试题
Linux的主要特性
Sep 03 面试题
Java程序员面试90题
Oct 19 面试题
北京捷通华声语音技术有限公司Java软件工程师笔试题
Apr 10 #面试题
顺丰快递Java软件工程师面试题
Jul 31 #面试题
Java软件工程师综合面试题笔试题
Sep 08 #面试题
JAVA软件工程师测试题
Jul 25 #面试题
请介绍一下WSDL的文档结构
Mar 17 #面试题
WSDL的操作类型主要有几种
Jul 19 #面试题
如何定义一个可复用的服务
Sep 30 #面试题
You might like
php摘要生成函数(无乱码)
2012/02/04 PHP
PHP截断标题且兼容utf8和gb2312编码
2013/09/22 PHP
php导出word文档与excel电子表格的简单示例代码
2014/03/08 PHP
php获得客户端浏览器名称及版本的方法(基于ECShop函数)
2015/12/23 PHP
PHP判断数组是否为空的常用方法(五种方法)
2017/02/08 PHP
PHP基于PDO调用sqlserver存储过程通用方法【基于Yii框架】
2017/10/07 PHP
PHP迭代器接口Iterator用法分析
2017/12/28 PHP
js隐藏与显示回到顶部按钮及window.onscroll事件应用
2013/01/25 Javascript
js随机颜色代码的多种实现方式
2013/04/23 Javascript
HTML,CSS,JavaScript速查表推荐
2014/12/02 Javascript
基于jQuery实现的QQ表情插件
2015/08/25 Javascript
推荐10 个很棒的 jQuery 特效代码
2015/10/04 Javascript
在WordPress中加入Google搜索功能的简单步骤讲解
2016/01/04 Javascript
基于javascript实现泡泡大冒险网页版小游戏
2016/03/23 Javascript
移动开发之自适应手机屏幕宽度
2016/11/23 Javascript
12306 刷票脚本及稳固刷票脚本(防挂)
2017/01/04 Javascript
详解打造 Vue.js 可复用组件
2017/03/24 Javascript
AngularJS表单验证功能
2017/10/19 Javascript
AngularJS使用ng-repeat遍历二维数组元素的方法详解
2017/11/11 Javascript
详解Angular-ui-BootStrap组件的解释以及使用
2018/07/13 Javascript
angularjs http与后台交互的实现示例
2018/12/21 Javascript
Python logging管理不同级别log打印和存储实例
2018/01/19 Python
pycharm 主题theme设置调整仿sublime的方法
2018/05/23 Python
Python数据可视化教程之Matplotlib实现各种图表实例
2019/01/13 Python
Django 实现xadmin后台菜单改为中文
2019/11/15 Python
keras读取训练好的模型参数并把参数赋值给其它模型详解
2020/06/15 Python
艺术用品:Arteza
2018/11/25 全球购物
不同浏览器创建XMLHttpRequest方法有什么不同
2014/11/17 面试题
档案检查欢迎词
2014/01/13 职场文书
设备动力科岗位职责范本
2014/02/23 职场文书
商业融资计划书
2014/04/29 职场文书
医药公司采购员岗位职责
2014/09/12 职场文书
社会主义核心价值观主题教育活动总结
2015/05/07 职场文书
解决jupyter notebook启动后没有token的坑
2021/04/24 Python
详细聊聊关于Mysql联合查询的那些事儿
2021/10/24 MySQL
类和原型的设计模式之复制与委托差异
2022/07/07 Javascript