Go语言的并发模型基于goroutine和channel,在多个goroutine同时运行并操作共享资源或通道时,很容易出现数据竞争、通道提前关闭、主程序提前退出等问题。这些问题会导致程序运行结果不符合预期,甚至直接崩溃。

sync.WaitGroup基础用法
sync.WaitGroup是Go标准库sync包提供的同步原语,用于等待一组goroutine执行完成。它有三个核心方法:
- Add(n int):设置需要等待的goroutine数量,n为正整数
- Done():每个goroutine执行完成后调用,将等待计数减1,等价于Add(-1)
- Wait():阻塞当前goroutine,直到等待计数变为0
使用时需要注意,Add方法要在启动goroutine之前调用,Done方法要放在goroutine的退出逻辑中,通常配合defer使用避免遗漏。
基础使用示例
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
// 设置等待2个goroutine
wg.Add(2)
go func() {
defer wg.Done()
fmt.Println("goroutine 1 执行完成")
}()
go func() {
defer wg.Done()
fmt.Println("goroutine 2 执行完成")
}()
// 等待所有goroutine完成
wg.Wait()
fmt.Println("所有goroutine执行完毕,主程序退出")
}
用sync.WaitGroup安全管理通道
并发场景下操作通道最常见的错误是:主程序在通道还有goroutine写入时就提前关闭,或者多个goroutine同时关闭通道,这些都会引发panic。使用sync.WaitGroup可以确保所有写入通道的goroutine都完成后,再关闭通道。
错误示例:通道提前关闭
下面的代码没有等待写入goroutine完成就关闭通道,会导致向已关闭的通道写入数据,触发panic:
package main
import "fmt"
func main() {
ch := make(chan int, 5)
// 启动写入goroutine
go func() {
for i := 0; i < 3; i++ {
ch <- i
}
}()
// 没有等待写入完成就关闭通道,会panic
close(ch)
// 读取通道数据
for v := range ch {
fmt.Println(v)
}
}
正确示例:配合WaitGroup管理通道
使用sync.WaitGroup等待所有写入goroutine完成后,再关闭通道,就可以避免上述问题:
package main
import (
"fmt"
"sync"
)
func main() {
ch := make(chan int, 5)
var wg sync.WaitGroup
// 启动3个写入goroutine
for i := 0; i < 3; i++ {
wg.Add(1)
go func(num int) {
defer wg.Done()
ch <- num
fmt.Printf("goroutine %d 写入数据完成n", num)
}(i)
}
// 启动一个goroutine等待所有写入完成后关闭通道
go func() {
wg.Wait()
close(ch)
fmt.Println("所有写入完成,通道已关闭")
}()
// 读取通道数据
for v := range ch {
fmt.Printf("读取到数据:%dn", v)
}
}
用sync.WaitGroup解决数据不一致问题
多个goroutine同时修改同一个共享变量时,会出现数据竞争,导致最终结果不符合预期。虽然解决数据不一致更推荐使用channel或者sync.Mutex,但sync.WaitGroup可以配合这些工具确保操作顺序正确,避免主程序提前读取未计算完成的结果。
问题示例:无同步的共享变量修改
下面的代码中,10个goroutine同时对共享变量count加1,由于没有同步,主程序可能在goroutine未完成时就读取count,最终结果大概率小于10:
package main
import "fmt"
func main() {
count := 0
// 启动10个goroutine修改count
for i := 0; i < 10; i++ {
go func() {
count++
}()
}
// 主程序没有等待,直接读取count
fmt.Printf("最终count值:%dn", count)
}
使用WaitGroup配合Mutex解决
这里用sync.Mutex保证同一时间只有一个goroutine修改count,再用sync.WaitGroup等待所有修改完成,就能得到正确结果:
package main
import (
"fmt"
"sync"
)
func main() {
count := 0
var wg sync.WaitGroup
var mu sync.Mutex
// 启动10个goroutine修改count
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
count++
mu.Unlock()
}()
}
// 等待所有goroutine修改完成
wg.Wait()
fmt.Printf("最终count值:%dn", count)
}
使用注意事项
- 不要拷贝sync.WaitGroup实例,拷贝会导致等待计数异常,建议传递指针或者使用全局变量
- Add方法的调用时机要在启动goroutine之前,避免Wait方法提前返回
- Done方法要确保每个goroutine都执行到,配合defer使用是最稳妥的方式
- WaitGroup只负责等待,不解决数据竞争问题,共享数据操作还需要配合channel或者锁机制
sync.WaitGroup是Go并发编程中非常实用的同步工具,合理使用可以有效协调goroutine的执行顺序,避免通道操作异常和程序提前退出的问题,是编写健壮并发程序的必备技能。
sync.WaitGroupgoroutinechannel数据不一致修改时间:2026-07-07 05:39:27