为何这些Goroutine的WaitGroup无法正常工作?如何避免死锁?
兄弟,我看你这是在Go里用WaitGroup的时候踩了死锁的坑,太常见了!我帮你捋捋最容易忽略的几个点,你对照自己的代码排查下:
首先得明确WaitGroup的核心逻辑:必须在启动Goroutine前调用wg.Add(n),每个子Goroutine结束时必须调用wg.Done(),最后在主Goroutine里用wg.Wait()等待所有子任务完成。你大概率是在这几个环节漏了细节:
Add调用时机太晚
要是你在启动Goroutine之后才调用wg.Add(1),甚至把Add放在子Goroutine里,主Goroutine可能已经先跑到wg.Wait()了。这时候WaitGroup计数是0,Wait会直接返回(导致子Goroutine还在跑主程序就退出),或者主Goroutine会一直等计数增加,直接死锁。
错误写法示例:var wg sync.WaitGroup go func() { wg.Add(1) // 错!Add在子Goroutine里,主Goroutine可能已经开始Wait了 defer wg.Done() // 业务逻辑 }() wg.Wait()正确姿势:启动前先Add
var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() // 业务逻辑 }() wg.Wait()Done调用不完整
你的Read...函数如果有分支逻辑(比如错误处理、提前return),很可能某些路径下没调用wg.Done(),导致WaitGroup的计数永远减不到0,主Goroutine就会一直卡着Wait,直接死锁。
解决办法超简单:把wg.Done()用defer放在子Goroutine的最开头,不管函数怎么退出(正常结束、报错return),都会执行Done:wg.Add(1) go func() { defer wg.Done() // 放在这里,稳得一批 reader := bufio.NewReader(os.Stdin) str, err := reader.ReadString('\n') if err != nil { fmt.Println("读取错误:", err) return // 这里return也会触发defer的Done } fmt.Println("读取内容:", str) }()Wait的位置不对
要是你把wg.Wait()放到了某个子Goroutine里,或者在循环启动Goroutine的时候逻辑乱了(比如Wait之后又启动新的Goroutine),也会出问题。Wait必须放在所有子Goroutine都启动完成之后,而且得在主Goroutine里执行。
最后给你个完整的正确示例,结合了接口和并发,你可以参考调整自己的代码:
package main import ( "bufio" "fmt" "os" "sync" ) // 定义Reader接口 type Reader interface { Read() string } // 实现StdinReader type StdinReader struct{} func (sr StdinReader) Read() string { reader := bufio.NewReader(os.Stdin) str, _ := reader.ReadString('\n') return str } func main() { var wg sync.WaitGroup reader := StdinReader{} // 启动3个读取Goroutine for i := 0; i < 3; i++ { wg.Add(1) // 启动前先加计数 go func(id int) { defer wg.Done() // 确保退出时执行Done content := reader.Read() fmt.Printf("Goroutine %d 读取到内容: %s", id, content) }(i) } wg.Wait() // 等待所有子Goroutine完成 fmt.Println("\n所有读取任务都完成啦!") }
内容的提问来源于stack exchange,提问作者alm2022

