在 Go语言并发编程 中,sync.WaitGroup 是一个非常常用的同步原语,用于等待一组 Goroutine 完成任务。然而,很多初学者甚至有经验的开发者都会在使用 WaitGroup 时犯一些典型错误,导致程序死锁、数据竞争或逻辑错误。
本文将通过通俗易懂的方式,结合代码示例,详细讲解 Go WaitGroup 教程 中常见的误用场景,并提供正确的解决方案,帮助你掌握安全、高效的并发控制技巧。
sync.WaitGroup 是 Go 标准库中的一个结构体,用于协调多个 Goroutine 的执行。它内部维护一个计数器:
Add(delta int):增加计数器Done():减少计数器(相当于 Add(-1))Wait():阻塞当前 Goroutine,直到计数器归零
这是最常见的错误之一。很多人以为可以在 Wait() 调用后再启动新的 Goroutine 并调用 Add(),但此时如果计数器已经为 0,Wait() 已经返回,再调用 Add() 会导致 panic。
// ❌ 错误示例:Wait() 后再 Add()package mainimport ( "fmt" "sync")func main() { var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() fmt.Println("Goroutine 1 done") }() wg.Wait() // 此时计数器归零,Wait 返回 // 下面这行会 panic:panic: sync: negative WaitGroup counter wg.Add(1) go func() { defer wg.Done() fmt.Println("Goroutine 2 done") }() wg.Wait()} 正确做法是:确保所有 Add() 调用都在对应的 Goroutine 启动前完成,并且不要在 Wait() 返回后再操作同一个 WaitGroup。
有些开发者习惯在 Goroutine 内部调用 Add(),这可能导致竞态条件(race condition),因为主 Goroutine 可能在子 Goroutine 调用 Add() 前就执行了 Wait(),从而提前返回。
// ❌ 错误示例:在 Goroutine 内调用 Add()package mainimport ( "fmt" "sync" "time")func main() { var wg sync.WaitGroup go func() { wg.Add(1) // 危险!可能在 Wait() 之后才执行 defer wg.Done() fmt.Println("Task done") }() time.Sleep(10 * time.Millisecond) // 试图“等待”Add 执行,不可靠! wg.Wait() // 可能立即返回(因为计数器仍为0)} ✅ 正确做法:始终在启动 Goroutine **之前** 调用 Add()。
// ✅ 正确示例wg.Add(1)go func() { defer wg.Done() fmt.Println("Task done")}()wg.Wait() 如果某个 Goroutine 没有调用 Done()(例如因 panic 提前退出),WaitGroup 的计数器永远不会归零,导致 Wait() 永久阻塞,程序卡死。
// ❌ 错误示例:panic 导致 Done() 未执行wg.Add(1)go func() { panic("something wrong!") wg.Done() // 永远不会执行}()wg.Wait() // 永久阻塞! ✅ 解决方案:使用 defer wg.Done() 确保无论函数如何退出,Done() 都会被调用。
在 Go并发同步 编程中,WaitGroup 是一个强大但需要谨慎使用的工具。牢记以下三点:
掌握这些要点,你就能避免 WaitGroup误用 带来的各种问题,写出更健壮的并发程序。
希望这篇 Go语言并发编程 教程对你有所帮助!欢迎实践并分享你的经验。
本文由主机测评网于2025-12-05发表在主机测评网_免费VPS_免费云服务器_免费独立服务器,如有疑问,请联系我们。
本文链接:https://www.vpshk.cn/2025123337.html