Promise 控制并发数

本文介绍了一个基于Promise实现的任务执行器,能够处理异步操作并限制并发执行数量。通过示例代码展示了如何一次性添加15个任务,执行器会确保所有任务完成后按顺序返回结果数组,同时最多执行4个任务。执行器适用于处理大量异步任务的场景,保证了任务执行的顺序性和并发控制。

问题引入

年前遇到这样一个现场写代码的笔试题目,奈何最后没整出来。后来回家经过几翻尝试,promise+sync最终得以实现

需要实现这样一个执行器:

  • 一次性向任务队列添加15个异步任务
  • 执行器在所有异步任务完成后,按任务顺序,在回调方法中传回任务结果数组
  • 执行器最多同时执行4个异步任务

都是码农,直接上代码

    // 定义15个任务,每个任务中都是异步操作
    const p1 = () => new Promise((resolve, reject) => setTimeout(() => reject(1), 1000))
    const p2 = () => Promise.resolve(2)
    const p3 = () => new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000))
    const p4 = () => Promise.resolve(4)
    const p5 = () => new Promise((resolve, reject) => setTimeout(reject, 2000, 'p5'))
    const p6 = () => Promise.resolve(6)
    const p7 = () => new Promise((resolve, reject) => setTimeout(() => resolve(7), 1000, 'p7'))
    const p8 = () => Promise.resolve(8)
    const p9 = () => new Promise((resolve, reject) => setTimeout(reject, 1000, 'p9'))
    const p10 = () => Promise.resolve(10)
    const p11 = () => new Promise((resolve, reject) => setTimeout(() => resolve(10), 2000, 'p10'))
    const p12 = () => Promise.resolve(12)
    const p13 = () => new Promise((resolve, reject) => setTimeout(reject, 1000, 'p11'))
    const p14 = () => Promise.resolve(14)
    const p15 = () => Promise.reject(15)
    /**
     *
     * @param task 任务队列
     * @param max 同时执行任务最大数
     * @returns {Promise<any>}
     */
    const handler = (task, max) => {
        return new Promise((resolve) => {
            let returned = []
            let len = task.length
            let current = 0;

            const check = () => {
                if (task.length) {
                    while (current < max) {
                        current++
                        run(task.length - 1, task.pop())
                    }
                } else {
                    if (len == 0) { // 当任务队列为空时,异步任务可能还未完成,
                        resolve(returned)
                    }
                }
            }

            function run(index, h) {
                let p = h(); // 若是同步任务则可以使用setTimeout将任务放到宏任务对列
                p.then(k => {
                    returned[index] = k
                    current--, len-- , check();
                }).catch(e => {
                    returned[index] = e
                    current--, len-- , check();
                })
            }

            check()
        })
    }

    const executor = async (task, max) => {
        let temp = await handler(task, max)
        return temp;

    }

    let taskArray = [p1, p2, p3, p4, p5, p6, p7, p8,
        p9, p10, p11, p12, p13, p14, p15]


    const now = Date.now()
    executor(taskArray, 4).then(uk => {
        console.log('---', uk) // 执行结果
        console.log(Date.now() - now) // 执行时间
    })
    // --- [ 1, 2, 2, 4, 'p5', 6, 7, 8, 'p9', 10, 10, 12, 'p11', 14, 15 ]
	// 3020

<think>我们讨论的是控制并发数的问题。用户提供了多个引用,展示了不同语言和工具中控制并发数的方法(Go、JMeter、JavaScript)。用户现在想了解如何控制并发数,但没有指定具体语言或场景。因此,我将从通用概念和常见实现方法入手,然后提供不同语言的示例。 ### 控制并发数的通用方法 控制并发数的核心思想是**限制同时执行的任务数量**。常见实现方式: 1. **信号量(Semaphore)**:通过计数器控制资源访问,如Go示例[^1] 2. **线程池/协程池**:预先创建固定数量的工作线程,如Java的ExecutorService 3. **令牌桶算法**:限制单位时间内可执行的任务数量 4. **队列管理**:将任务放入队列,由调度器控制取出执行的速度 ### 不同语言实现示例 #### Go语言(使用带缓冲的channel作为信号量)[^1] ```go package main import ( "fmt" "time" ) // 全局信号量,控制最大并发数为3 var sem = make(chan bool, 3) func worker(id int) { sem <- true // 获取信号量(如果已满则阻塞) defer func() { <-sem }() // 释放信号量 fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Second) // 模拟工作 fmt.Printf("Worker %d done\n", id) } func main() { for i := 1; i <= 10; i++ { go worker(i) } time.Sleep(5 * time.Second) // 等待所有goroutine完成(实际应用中需用sync.WaitGroup) } ``` #### Java(使用ExecutorService线程池) ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConcurrencyControl { public static void main(String[] args) { // 创建固定大小为3的线程池 ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 1; i <= 10; i++) { int taskId = i; executor.submit(() -> { System.out.println("Task " + taskId + " started by " + Thread.currentThread().getName()); try { Thread.sleep(1000); // 模拟工作 } catch (InterruptedException e) {} System.out.println("Task " + taskId + " finished"); }); } executor.shutdown(); // 关闭线程池(等待已提交任务完成) } } ``` #### JavaScript(使用Promise控制并发)[^3][^4] ```javascript async function runWithConcurrency(tasks, limit) { const results = []; const executing = []; // 当前正在执行的Promise集合 for (const task of tasks) { // 创建任务Promise(不立即执行) const p = task().then(result => { // 任务完成后从executing中移除 executing.splice(executing.indexOf(p), 1); return result; }); executing.push(p); results.push(p); // 当达到并发上限时,等待任意一个任务完成 if (executing.length >= limit) { await Promise.race(executing); } } return Promise.all(results); } // 使用示例 const tasks = Array(10).fill(null).map((_, i) => () => new Promise(resolve => { console.log(`任务${i}开始`); setTimeout(() => resolve(i), Math.random() * 2000); }) ); runWithConcurrency(tasks, 3).then(results => { console.log('所有任务完成:', results); }); ``` ### 关键参数说明 | 参数 | 作用说明 | 典型设置场景 | |------------|----------------------------------|--------------------------| | 并发数 | 同时执行的最大任务数量 | CPU密集型:核心数+1~2 | | | | IO密集型:可设较高值(如100)| | 超时控制 | 防止任务长时间阻塞 | 网络请求:5~30秒 | | 队列长度 | 等待执行的任务缓冲数量 | 根据内存和需求设定 | ### 应用场景建议 1. **Web爬虫**:控制同时发起的网络请求数[^2] 2. **批量数据处理**:避免数据库连接耗尽 3. **API调用**:遵守第三方服务的速率限制 4. **资源敏感操作**:如大文件处理,避免内存溢出 --- §§相关问题§§ 1. 如何根据系统资源动态调整并发数? 2. 高并发场景下如何避免任务饿死? 3. 控制并发数时如何处理任务优先级? 4. 分布式系统中如何实现全局并发控制?</think>### 控制并发数的核心方法与实现 控制并发数的核心是**限制同时执行的任务数量**,避免资源耗尽或系统过载。以下是通用实现方法(语言无关)和主流语言示例: #### 核心原理(三种实现模式) 1. **信号量模式** 通过计数器控制资源访问(如Go示例[^1]) ```mermaid graph LR A[任务队列] --> B{信号量计数器} B -- 计数器>0 --> C[执行任务] C --> D[计数器-1] D --> E[任务完成] E --> F[计数器+1] B -- 计数器=0 --> G[等待释放] ``` 2. **线程池/协程池模式** 预创建固定数量的工作单元(如Java线程池) 3. **队列调度模式** 使用任务队列+调度器控制(如JS示例[^4]) --- ### 各语言具体实现 #### Go语言(信号量模式)[^1] ```go // 全局并发控制器(最大并发数=3) var sem = make(chan bool, 3) func worker(id int) { sem <- true // 获取令牌(阻塞直到有空位) defer func() { <-sem }() // 释放令牌 fmt.Printf("Worker %d started\n", id) time.Sleep(time.Second * 2) // 模拟任务执行 fmt.Printf("Worker %d done\n", id) } func main() { for i := 1; i <= 10; i++ { go worker(i) } time.Sleep(time.Second * 10) // 等待所有任务完成 } ``` #### Java(线程池模式) ```java ExecutorService executor = Executors.newFixedThreadPool(3); // 最大并发数=3 for (int i = 1; i <= 10; i++) { int taskId = i; executor.submit(() -> { System.out.println("Task " + taskId + " started"); Thread.sleep(2000); // 模拟任务 System.out.println("Task " + taskId + " completed"); return null; }); } executor.shutdown(); // 优雅关闭 ``` #### JavaScriptPromise调度器)[^4] ```javascript class ConcurrencyController { constructor(maxConcurrent) { this.max = maxConcurrent; this.queue = []; this.running = 0; } add(task) { return new Promise((resolve, reject) => { this.queue.push(() => task().then(resolve).catch(reject)); this.run(); }); } run() { while (this.running < this.max && this.queue.length) { const task = this.queue.shift(); this.running++; task().finally(() => { this.running--; this.run(); // 检查是否有待执行任务 }); } } } // 使用示例 const controller = new ConcurrencyController(3); for (let i = 1; i <= 10; i++) { controller.add(() => new Promise(r => setTimeout(() => { console.log(`Task ${i} done`); r(); }, 1000) )); } ``` #### Python(协程信号量) ```python import asyncio sem = asyncio.Semaphore(3) # 最大并发数=3 async def worker(id): async with sem: # 自动获取/释放信号量 print(f"Worker {id} started") await asyncio.sleep(2) print(f"Worker {id} done") async def main(): tasks = [worker(i) for i in range(1, 11)] await asyncio.gather(*tasks) asyncio.run(main()) ``` --- ### 关键参数配置建议 | 参数 | 说明 | 推荐值 | |--------------|-------------------------------|---------------------------| | **并发数** | 同时执行的任务上限 | CPU密集型:CPU核心数+1 | | | | IO密集型:50-100+ | | **队列长度** | 等待执行的任务缓冲区大小 | 并发数的2-5倍 | | **超时时间** | 单任务最大执行时间 | 根据业务特性设置(如30s) | > 实际案例:Web爬虫通常设置并发数在20-100之间,避免被目标网站封禁[^2] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值