You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Go通道正确控制并发?限制goroutine数量问题求助

问题:如何用Go通道限制goroutine并发数为3?

我是Go并发编程的新手,正在学习如何使用通道(channel)控制并发。我希望实现这样的功能:在循环中每次启动一个新的goroutine调用write函数,同时限制并发运行的goroutine数量为3个。

我的首次尝试代码如下:

import (
    "fmt"
    "time"
)

func write(val int, ch chan bool) { 
    fmt.Println("Processing:", val) 
    time.Sleep(2 * time.Second) 
    ch <- val % 3 == 0 
} 

func main() { 
    ch := make(chan bool, 3) // 原本以为这样能限制到3个goroutine?
    for i := 0; i< 10; i++ { 
        go write(i, ch) 
        resp := <- ch 
        fmt.Println("Divisible by 3:", resp) 
    } 
    time.Sleep(20 * time.Second) 
}

我原本以为这段代码会每次启动3个goroutine执行write,待前3个完成后再启动下一批,但从日志来看实际每次仅运行一个goroutine。请问需要修改哪些地方才能实现我想要的功能?


解决方案

嘿,我来帮你捋清楚这个问题~你现在的代码之所以每次只跑一个goroutine,是因为循环里的执行顺序搞反了:你每启动一个goroutine之后,立刻就去通道里接收它的结果,这会让主goroutine直接阻塞,直到这个刚启动的goroutine跑完发回结果,才会继续下一轮循环启动下一个goroutine。相当于你是在串行启动并等待每个goroutine完成,自然达不到并发3个的效果。

要实现最多3个goroutine同时运行的需求,我们可以用「令牌桶」的思路来控制并发:先往一个带缓冲的通道里放3个“令牌”,每次启动goroutine前必须先拿到一个令牌,goroutine执行完之后再把令牌放回通道,这样就能保证同一时间最多有3个goroutine在跑。

修改后的代码如下:

import (
    "fmt"
    "time"
)

func write(val int, tokenChan chan struct{}, resultChan chan bool) {
    fmt.Println("Processing:", val)
    time.Sleep(2 * time.Second)
    // 把计算结果发送到结果通道
    resultChan <- val%3 == 0
    // 执行完成,放回令牌,让下一个goroutine可以启动
    tokenChan <- struct{}{}
}

func main() {
    maxConcurrency := 3
    // 创建令牌通道,缓冲大小就是最大并发数
    tokenChan := make(chan struct{}, maxConcurrency)
    // 先往通道里放满令牌
    for i := 0; i < maxConcurrency; i++ {
        tokenChan <- struct{}{}
    }

    // 创建结果通道,缓冲足够容纳所有结果,避免阻塞goroutine
    resultChan := make(chan bool, 10)

    // 启动所有goroutine
    for i := 0; i < 10; i++ {
        // 先取令牌,没有令牌就阻塞,直到有goroutine放回
        <-tokenChan
        go write(i, tokenChan, resultChan)
    }

    // 收集所有结果,同时等待所有goroutine完成
    for i := 0; i < 10; i++ {
        resp := <-resultChan
        fmt.Println("Divisible by 3:", resp)
    }
}

给你拆解一下这个逻辑:

  • 用tokenChan作为令牌容器,空结构体struct{}{}是最优选择,因为它不占用任何内存空间,完全适合做“令牌”这种只需要占位的东西。
  • 主循环里,每次启动goroutine前必须先从tokenChan取走一个令牌——如果通道里已经没有令牌了,主goroutine就会暂停,直到某个正在运行的goroutine完成并把令牌放回来。
  • 每个write goroutine跑完业务逻辑后,会把令牌放回通道,这样主循环就能继续启动下一个goroutine了。
  • 最后单独收集所有结果,不仅能拿到每个goroutine的执行结果,还能优雅地等待所有goroutine完成,再也不用靠硬编码time.Sleep来碰运气啦~

另外,如果你之后需要更复杂的并发控制,也可以看看sync.WaitGroup或者第三方的并发池库,但这个令牌桶的方式已经完全能满足你当前的需求啦。


内容的提问来源于stack exchange,提问作者Abe Miessler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:12:03