如何用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完成并把令牌放回来。 - 每个
writegoroutine跑完业务逻辑后,会把令牌放回通道,这样主循环就能继续启动下一个goroutine了。 - 最后单独收集所有结果,不仅能拿到每个goroutine的执行结果,还能优雅地等待所有goroutine完成,再也不用靠硬编码
time.Sleep来碰运气啦~
另外,如果你之后需要更复杂的并发控制,也可以看看sync.WaitGroup或者第三方的并发池库,但这个令牌桶的方式已经完全能满足你当前的需求啦。
内容的提问来源于stack exchange,提问作者Abe Miessler
相关产品推荐
相关产品推荐

