控制并发

202 阅读3分钟

什么是并发

并发(Concurrency)  是指在同一时间段内处理多个任务的能力。并发并不意味着多个任务同时执行,而是指任务之间可以交替执行,从而给人一种“同时进行”的错觉。并发的主要目的是提高系统的资源利用率和响应能力。

这么说可能不是很生动,我们就来举个生动点的例子:

假设你是一家餐厅的厨师,厨房里有多个任务需要完成:

  1. 煮汤(需要 10 分钟)
  2. 煎牛排(需要 5 分钟)
  3. 烤面包(需要 3 分钟)
  4. 洗餐具(需要 7 分钟)

你的目标是尽快完成所有任务,让顾客能够尽快用餐。

1. 单线程(非并发)

如果你是一个厨师,只能一次做一件事,那么你需要按顺序完成所有任务:

  • 煮汤(10 分钟)
  • 煎牛排(5 分钟)
  • 烤面包(3 分钟)
  • 洗餐具(7 分钟)

总时间:10 + 5 + 3 + 7 = 25 分钟

这种方式效率很低,因为你在煮汤的时候,煎锅和烤箱都是空闲的。

2. 并发(多线程)

现在,假设你有 3 个厨师(相当于 3 个线程),可以同时处理多个任务:

  • 厨师 A:煮汤(10 分钟)
  • 厨师 B:煎牛排(5 分钟)
  • 厨师 C:烤面包(3 分钟)

当厨师 B 和厨师 C 完成任务后,他们可以帮忙做其他事情:

  • 厨师 B:煎完牛排后,开始洗餐具(7 分钟)
  • 厨师 C:烤完面包后,可以休息或帮忙其他任务。

总时间:10 分钟(因为煮汤是最耗时的任务,其他任务可以在这段时间内完成)。

实现控制并发

思路:需要一个任务队列,将任务放入任务队列中直到任务个数等于最大并发数,当有任务完成后出任务队列 ,新任务进队列

我们用ajax(time)来模拟一个要耗时的任务,addTask函数用来将任务添加到任务队列中

function ajax(time){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}

function addTask(time,name){
    limit.add(()=>ajax(time)
    .then(()=>{
        console.log(`任务${name}完成`);       
    }).catch((err)=>{
        console.log(`任务${name}失败`); 
    })
)}

具体用法如下:

//这么使用,// 控制并发请求数为2
const limit = new Limit(2);


addTask(10000,1);
addTask(5000,2);
addTask(1000,3);
addTask(3000,4);
addTask(7000,5);

构造函数我们用class来写,

class Limit{
    constructor(paralleCount=2){//并发数默认2
        this.tasks=[];
        this.runningCount=0;//正在执行的任务数
        this.paralleCount=paralleCount;//并发数
    }
    add(task){
        return new Promise((resolve, reject) => {
            this.tasks.push({
                task,
                resolve,
                reject
            })//添加5个任务
            this._run();//执行任务,如果在后面resolve,因为_run耗时,会直接resolve()
        });
    }
    _run(){//执行任务
        while(this.runningCount<this.paralleCount && this.tasks.length){
            const {task,resolve,reject} = this.tasks.shift();//取出任务执行
            this.runningCount++;
            task().then(()=>{//一个任务执行完毕,runningCount减1
                resolve();
                this.runningCount--;
                this._run();
            }).catch((err)=>{
                reject(err);
                this.runningCount--;
                this._run();
            })
        }
    }
}

现在我来解释一下这个代码:首先class写构造函数要构造器,paralleCount=2如果不给参数默认并发数为2,构造器里创建tasks,runningCount,paralleCount属性,方法add()参数为函数,因为后面加上.then所以要返回promise对象,.then里的函数要在promise对象的状态变更后才执行.add函数将任务加入任务队列,5个addtask调用将5个任务并将resolve和rejrect函数进入队列.接下来就是执行了.如果条件成立,就将任务在队列解析出来,runningCount+1,任务执行完毕后也会返回promsie对象,执行结果用try/catch捕获,成功则将执行resolve,更改add函数返回的promise对象的状态,执行打印,反之亦然.runningCount-1,进行递归.

结果:

image.png