1. 引言
并发任务执行是多线程编程中的一个重要主题,当应用程序需要同时处理多个任务时,如果不加以控制,可能会导致资源过度消耗或系统负载过高。通俗的讲,就是前端有一万个请求,后端一次只能处理五十个,那么要如何控制并发确保系统稳定运行呢?对于前端开发来说,js是单线程机制已经是一个伪命题了,我们通过异步操作,可以合理地限制并发任务的数量,确保系统稳定运行,同时提高资源利用率。 下面是设计思路以及具体实现:
2. 设计并发任务控制器
我们的目标是创建一个类 SuperTask
,它可以接受任务并将它们按照指定的并发数量进行调度执行。每个任务都是一个返回 Promise
的函数,这样我们可以轻松地处理异步操作。
2.1 构造函数
类中添加构造函数,初始化 SuperTask
对象的属性:
parallelCount
:用户可以通过传入参数来设置最大并发数量。默认值为2,用于接下来的测试。tasks
:一个数组,用于存储等待执行的任务对象。runningCount
:一个计数器,记录当前正在执行的任务数量。
class SuperTask { constructor(parallelCount = 2) { this.parallelCount = parallelCount; // 并发量 this.tasks = []; // 待执行的任务队列 this.runningCount = 0; // 正在运行的任务数量 } }
2.2 add
方法
类中添加add
方法,接收一个任务(即返回 Promise
的函数),将其封装成一个任务对象,并添加到任务队列中。之后调用 _run
方法尝试运行队列中的任务。
// 添加任务到队列 add(task) { return new Promise((resolve, reject) => { this.tasks.push({ task, resolve, reject }); this._run(); }); }
2.3 _run
方法
类中添加_run
方法,这是一个私有方法,用于检查是否有可用的并发槽位,并运行队列中的任务。它的工作流程如下:
检查当前正在运行的任务数量是否小于最大并发数量。
如果小于最大并发数量,并且任务队列中有任务,则从队列中取出一个任务并执行。
执行任务后,无论结果如何(成功或失败),都会调用
.finally()
回调来减少正在运行的任务数量,并再次调用_run
方法尝试运行队列中的下一个任务。
// 运行任务队列中的任务 _run() { while (this.runningCount < this.parallelCount && this.tasks.length > 0) { const { task, resolve, reject } = this.tasks.shift(); this.runningCount++; task().then(resolve, reject).finally(() => { this.runningCount--; this._run(); }); } }
2.4 使用示例
假设我们有一个简单的延时函数 timeout
,用于模拟异步操作。
function timeout(time) { return new Promise((resolve) => { setTimeout(() => { resolve(); }, time); }); }
接下来,我们可以创建一个 SuperTask
实例,并添加一些任务:
const superTask = new SuperTask(); function addTask(time, name) { superTask.add(() => timeout(time)).then(() => { console.log(`任务${name}完成`); }); } addTask(10000, '1'); addTask(2000, '2'); addTask(5000, '3'); addTask(1000, '4'); addTask(7000, '5'); addTask(3000, '6');
总结
并发任务控制器 SuperTask
,通过限制并发任务的数量来确保系统的稳定性和效率。该控制器通过维护一个任务队列和一个运行计数器,确保在不超过设定的最大并发数量的前提下,有序地执行任务,从而实现高效的并发控制。并发任务控制器的核心在于_run()
的设计,即把任务都装在任务队列里,只取并发量的任务,某任务结束之后,通过递归再从队列里取任务,希望友友们在面试中遇到这类问题可以有一定的思路。