使用sync.WaitGroup时有没有更优的for循环退出方案?
问题分析
- 你原来直接在
orders >= quantity判断块内跳出循环会报错的核心原因:已经执行了sem <- true占用了1个信号量配额,直接跳出循环后没有对应协程执行<-sem释放配额,会导致后续逻辑异常 - 布尔变量
e存在可见性风险:对e的写入加了锁但读取没有加锁,多核场景下可能出现e已经被赋值为true但循环未及时感知的问题 - 原代码末尾的信号量排空循环存在死锁风险:
wgnested.Wait()已经保证所有子协程执行完成,所有信号量配额都已释放,额外的排空逻辑完全冗余,当信号量占满时还会触发死锁
优化方案
Go语言标准的并发停止信号方案是使用context.WithCancel,比布尔变量更安全、无竞态,也不会触发panic,修改后的代码如下:
package main import ( "context" "fmt" "math/rand" "strconv" "sync" ) func main() { var wg sync.WaitGroup var accounts int = 1 var emails int = 1000 for i := 0; i < accounts; i++ { wg.Add(1) go func(i int) { defer wg.Done() threads := 500 // 最大并发子协程数 var orderMutex sync.Mutex var quantity = 2 var orders = 0 sem := make(chan bool, threads) var wgnested sync.WaitGroup // 创建可取消的context作为停止信号 ctx, cancel := context.WithCancel(context.Background()) defer cancel() for x := 0; x < emails; x++ { // 先检查是否已经触发停止,避免无意义的信号量占用 select { case <-ctx.Done(): break default: } sem <- true // 占用信号量 orderMutex.Lock() if orders >= quantity { fmt.Println(strconv.Itoa(x) + " 达到数量上限,停止循环") orderMutex.Unlock() cancel() // 发送停止信号 <-sem // 释放当前占用的信号量,避免泄漏 break } orderMutex.Unlock() wgnested.Add(1) go func() { defer wgnested.Done() defer func() { <-sem }() // 模拟下单逻辑 if rand.Intn(50) == 7 { orderMutex.Lock() orders++ orderMutex.Unlock() } fmt.Println(strconv.Itoa(i) + " 执行完成 ") }() } wgnested.Wait() fmt.Println(strconv.Itoa(i) + " 总下单数: " + strconv.Itoa(orders)) }(i) } wg.Wait() }
优化点说明
- 用
context作为停止信号,天然保证并发安全,不存在布尔变量的可见性问题 - 触发停止条件时主动释放当前已占用的信号量,避免资源泄漏
- 移除了冗余的信号量排空逻辑,依赖
wgnested.Wait()保证所有子协程执行完成,消除死锁风险 - 循环开头增加停止信号判断,避免达到停止条件后无意义的信号量占用和逻辑执行
内容的提问来源于stack exchange,提问作者Nick D
相关产品推荐
相关产品推荐

