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...

面试题 相关文章推荐
Java面试题及答案
Sep 08 面试题
Java面试中常遇到的问题,也是需要注意的几点
Aug 30 面试题
extern在函数声明中是什么意思
Jan 19 面试题
c++工程师面试问题
Aug 04 面试题
OLEDBConnection和SQLConnection有什么区别
May 31 面试题
C#里面如何倒序排列一个数组的元素?
Jun 21 面试题
linux面试题参考答案(9)
Jan 29 面试题
ShellScript面试题一则-ShellScript编程
Jun 24 面试题
解释一下钝化(Swap out)
Dec 26 面试题
不同浏览器创建XMLHttpRequest方法有什么不同
Nov 17 面试题
介绍一下如何优化MySql
Dec 20 面试题
Delphi软件工程师试题
Jan 29 面试题
北京捷通华声语音技术有限公司Java软件工程师笔试题
Apr 10 #面试题
顺丰快递Java软件工程师面试题
Jul 31 #面试题
Java软件工程师综合面试题笔试题
Sep 08 #面试题
JAVA软件工程师测试题
Jul 25 #面试题
请介绍一下WSDL的文档结构
Mar 17 #面试题
WSDL的操作类型主要有几种
Jul 19 #面试题
如何定义一个可复用的服务
Sep 30 #面试题
You might like
人工智能开始玩《星际争霸2》 你的操作跟得上吗?
2017/08/11 星际争霸
一个用于网络的工具函数库
2006/10/09 PHP
php中mkdir函数用法实例分析
2014/11/15 PHP
关于扩展 Laravel 默认 Session 中间件导致的 Session 写入失效问题分析
2016/01/08 PHP
PHP中函数gzuncompress无法使用的解决方法
2017/03/02 PHP
Laravel如何自定义command命令浅析
2019/03/23 PHP
javaScript - 如何引入js代码
2021/03/09 Javascript
谷歌浏览器 insertCell与appendChild的区别
2009/02/12 Javascript
jQuery ajax BUG:object doesn't support this property or method
2010/07/06 Javascript
js判断背景图片是否加载成功使用img的width实现
2013/05/29 Javascript
nodejs文件操作模块FS(File System)常用函数简明总结
2014/06/05 NodeJs
js获取当前日期前七天的方法
2015/02/28 Javascript
jQuery实现TAB选项卡切换特效简单演示
2016/03/04 Javascript
javascript中this指向详解
2016/04/23 Javascript
基于JS实现横线提示输入验证码随验证码输入消失(js验证码的实现)
2016/10/27 Javascript
nodejs实例解析(输出hello world)
2017/01/03 NodeJs
vue项目打包后打开页面空白解决办法
2018/06/29 Javascript
Vue中的基础过渡动画及实现原理解析
2018/12/04 Javascript
node.js实现为PDF添加水印的示例代码
2018/12/05 Javascript
JS根据json数组多个字段排序及json数组常用操作
2019/06/06 Javascript
解决vue.js提交数组时出现数组下标的问题
2019/11/05 Javascript
vue a标签点击实现赋值方式
2020/09/07 Javascript
原生JavaScript实现幻灯片效果
2021/02/19 Javascript
Python wxPython库消息对话框MessageDialog用法示例
2018/09/03 Python
python实现自动登录后台管理系统
2018/10/18 Python
PyTorch中的padding(边缘填充)操作方式
2020/01/03 Python
基于pytorch的lstm参数使用详解
2020/01/14 Python
Python基于pyjnius库实现访问java类
2020/07/31 Python
python 5个顶级异步框架推荐
2020/09/09 Python
创业计划书中要认真思考的问题
2013/12/28 职场文书
《北京的春节》教学反思
2014/04/07 职场文书
2016年小学生寒假家长评语
2015/10/10 职场文书
2016年世界艾滋病日宣传活动总结
2016/04/01 职场文书
新学期小学班主任工作计划
2019/06/21 职场文书
浅谈GO中的Channel以及死锁的造成
2022/03/18 Golang
HTML常用标签超详细整理
2022/03/19 HTML / CSS