前言
最近看到很多面经分享中都出现过这么一道题目
实现一个类或函数,使其支持最大并发数为2
这个问题看上去非常实用啊,如果说大型应用中需要批量发起大量的请求,浏览器单域名
在 http1.1
以下最多只能共存6
条TCP连接,有可能会阻塞很多重要的任务,比如下载或者上传,你需要控制并发数量,就需要自行实现一个任务调度器
思考
- 实现这么一个东西肯定是需要一个任务队列的
- 这个队列只能同时取出n个任务来执行
- 当任务结束后立即取下一个任务开始执行,直到任务队列为空
实现
按照上面的思路,我们先设计这个对象
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 代码完整版+测试用例