什么是并发
并发(Concurrency) 是指在同一时间段内处理多个任务的能力。并发并不意味着多个任务同时执行,而是指任务之间可以交替执行,从而给人一种“同时进行”的错觉。并发的主要目的是提高系统的资源利用率和响应能力。
这么说可能不是很生动,我们就来举个生动点的例子:
假设你是一家餐厅的厨师,厨房里有多个任务需要完成:
- 煮汤(需要 10 分钟)
- 煎牛排(需要 5 分钟)
- 烤面包(需要 3 分钟)
- 洗餐具(需要 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,进行递归.
结果: