Golang异步编程:为何需用WaitGroup而非仅依赖Channel?
其实这俩不是绑定必须一起用的,得看你具体的需求场景——你说的三个API调用场景,只用channel确实能搞定,但有些场景下WaitGroup会更合适,甚至是必需的。
先说说你的场景:只用channel完全可行
就像你说的,启动三个goroutine分别调用API,每个goroutine把结果发送到对应的channel,然后主goroutine依次从三个channel读结果,确实能阻塞等到所有数据返回,代码大概是这样:
func main() { ch1 := make(chan APIResult) ch2 := make(chan APIResult) ch3 := make(chan APIResult) go callAPI1(ch1) go callAPI2(ch2) go callAPI3(ch3) result1 := <-ch1 result2 := <-ch2 result3 := <-ch3 // 用三个结果构建最终数据结构 }
这种情况下,确实不需要WaitGroup,逻辑清晰,代码也简洁。
那什么时候需要WaitGroup?
1. 不需要goroutine返回值,只需要等所有任务完成
比如你要批量执行100个日志写入操作,不需要拿返回值,只需要确保所有日志都写完再退出程序。这时候用WaitGroup比创建100个channel或者一个空channel要更简洁:
func main() { var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func(idx int) { defer wg.Done() writeLog(idx) // 无返回值的任务 }(i) } wg.Wait() // 等所有日志写完再退出 }
2. 配合channel处理动态数量的goroutine,避免死锁
如果你的goroutine数量是动态生成的(比如根据用户输入的数量创建),用一个channel接收所有结果,然后用range遍历channel的话,必须要等所有goroutine都发送完结果后关闭channel,不然range会一直阻塞等新数据。这时候WaitGroup就能帮你实现“所有goroutine完成后关闭channel”:
func main() { resultCh := make(chan APIResult) var wg sync.WaitGroup apiList := getDynamicAPIList() // 动态获取API列表,数量不确定 for _, api := range apiList { wg.Add(1) go func(apiURL string) { defer wg.Done() res := callAPI(apiURL) resultCh <- res }(api) } // 启动一个goroutine等所有任务完成后关闭channel go func() { wg.Wait() close(resultCh) }() // 遍历所有结果 for res := range resultCh { // 处理每个结果 } }
这种场景下,如果不用WaitGroup,你没法确定什么时候该关闭channel,直接在主goroutine里关的话,可能有些goroutine还没发送数据,导致发送操作阻塞甚至死锁。
3. 需要统一等待所有goroutine完成后做清理工作
比如你启动了多个goroutine操作同一个资源(比如一个数据库连接池),需要等所有goroutine都用完资源后,再关闭连接池。这时候WaitGroup可以帮你在所有任务完成后触发清理逻辑,而不用依赖channel的接收操作。
总结一下
- 当你明确需要获取每个goroutine的返回值,且数量固定:直接用channel就行,不需要WaitGroup。
- 当你不需要返回值,只需要等待所有任务完成,或者goroutine数量动态变化,需要配合channel做批量结果处理:这时候WaitGroup会更合适。
说白了,这俩都是同步goroutine的工具,只是适用场景不同,不是非此即彼的关系,有时候也会一起用(比如上面的动态goroutine+channel的例子)。
内容的提问来源于stack exchange,提问作者BitQueen

