写点什么

sync.WaitGroup:掌握并发编程中的重要工具

作者:Jack
  • 2023-04-04
    广东
  • 本文字数:2647 字

    阅读完需:约 9 分钟

sync.WaitGroup:掌握并发编程中的重要工具

在 Go 语言中,sync.WaitGroup 是一种用于等待一组 Goroutine 完成执行的机制。WaitGroup 的基本用法是通过 Add()方法增加等待的 Goroutine 数量,通过 Done()方法减少等待数量,最后通过 Wait()方法等待所有的 Goroutine 执行完毕。这些操作都是线程安全的,可以在多个 Goroutine 中并发使用。


除了基本用法,sync.WaitGroup 还有一些高级用法和技巧,可以更好地控制 Goroutine 的执行和等待。下面介绍几种常见的高级用法。

1. 嵌套使用 WaitGroup

在某些情况下,我们需要等待多个 Goroutine 完成后再开始下一步操作,而这些 Goroutine 又分别包含多个子 Goroutine。这时,我们可以使用嵌套的 WaitGroup 来控制。


func main() {    var wg sync.WaitGroup    for i := 0; i < 3; i++ {        wg.Add(1)        go func(index int) {            var subWg sync.WaitGroup            for j := 0; j < 5; j++ {                subWg.Add(1)                go func(index int, subIndex int) {                    defer subWg.Done()                    time.Sleep(time.Second)                    fmt.Printf("Goroutine %d-%d done\n", index, subIndex)                }(index, j)            }            subWg.Wait()            fmt.Printf("Goroutine %d done\n", index)            wg.Done()        }(i)    }    wg.Wait()    fmt.Println("All done")}
复制代码


在这个示例中,我们启动了 3 个 Goroutine,每个 Goroutine 又包含了 5 个子 Goroutine。我们使用了一个嵌套的 WaitGroup 来控制子 Goroutine 的执行和等待,最后再使用外层的 WaitGroup 来控制所有 Goroutine 的执行和等待。

2. 使用 WaitGroup 实现限流

在某些情况下,我们需要限制 Goroutine 的并发数量,以避免资源过度消耗或者其他问题。这时,我们可以使用 WaitGroup 和 channel 来实现限流。


func main() {    var wg sync.WaitGroup    limit := make(chan struct{}, 3)    for i := 0; i < 10; i++ {        wg.Add(1)        limit <- struct{}{}        go func(index int) {            defer func() {                wg.Done()                <-limit            }()            time.Sleep(time.Second)            fmt.Printf("Goroutine %d done\n", index)        }(i)    }    wg.Wait()    fmt.Println("All done")}
复制代码


在这个示例中,我们启动了 10 个 Goroutine,并使用一个长度为 3 的 channel 来实现限流。每个 Goroutine 在开始执行之前,需要从 channel 中获取一个 token,表示当前还有可用的并发数。当 Goroutine 执行完毕后,需要将 token 归还到 channel 中,以便其他 Goroutine 可以使用。

3. 优雅地关闭 Goroutine

在某些情况下,我们需要优雅地关闭一组 Goroutine,以避免资源泄漏或者其他问题。这时,我们可以使用 WaitGroup 和 context 来实现优雅的关闭。


func main() {    var wg sync.WaitGroup    ctx, cancel := context.WithCancel(context.Background())    for i := 0; i < 3; i++ {        wg.Add(1)        go func(index int) {            defer wg.Done()            for {                select {                case <-ctx.Done():                    fmt.Printf("Goroutine %d closed\n", index)                    return                default:                    time.Sleep(time.Second)                    fmt.Printf("Goroutine %d working\n", index)                }            }        }(i)    }    time.Sleep(3 * time.Second)    cancel()    wg.Wait()    fmt.Println("All done")}
复制代码


这段代码创建了 3 个 Goroutine,并使用 sync.WaitGroup 和 context.Context 来等待它们完成执行。每个 Goroutine 都会持续工作,每秒输出一次"working",直到收到 ctx 的关闭信号。在 main 函数中,我们休眠 3 秒钟后,调用 cancel 函数来关闭 ctx,从而通知所有 Goroutine 停止工作。最后,使用 wg.Wait()等待所有 Goroutine 完成执行,并输出"All done"。

4.使用 sync.Once 实现单次执行

在某些情况下,我们需要保证某个操作只被执行一次,而不管有多少个 Goroutine 需要执行。这时,我们可以使用 sync.Once 来实现单次执行。


var once sync.Once
func main() { var wg sync.WaitGroup for i := 0; i < 3; i++ { wg.Add(1) go func(index int) { defer wg.Done() once.Do(func() { fmt.Println("Do something only once") }) fmt.Printf("Goroutine %d done\n", index) }(i) } wg.Wait() fmt.Println("All done")}
复制代码


在这个示例中,我们使用了一个全局的 sync.Once 变量来保证某个操作只被执行一次。在每个 Goroutine 中,我们使用 once.Do 方法来执行这个操作,只有第一个调用 Do 方法的 Goroutine 会执行这个操作,其他 Goroutine 直接跳过。

5.使用 sync.Pool 优化内存分配

在某些情况下,我们需要频繁地分配和释放某种类型的对象,这会导致内存申请和释放的开销比较大。这时,我们可以使用 sync.Pool 来优化内存分配。


type MyObject struct {    data []byte}
func (o *MyObject) Reset() { o.data = o.data[:0]}
var pool = &sync.Pool{ New: func() interface{} { return &MyObject{data: make([]byte, 1024)} },}
func main() { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(index int) { defer wg.Done() obj := pool.Get().(*MyObject) defer func() { obj.Reset() pool.Put(obj) }() time.Sleep(time.Second) fmt.Printf("Goroutine %d working\n", index) }(i) } wg.Wait() fmt.Println("All done")}
复制代码


在这个示例中,我们定义了一个 MyObject 类型,并使用 sync.Pool 来管理它的内存。在每个 Goroutine 中,我们从 pool 中获取一个 MyObject 对象,执行需要的操作,然后把对象归还到 pool 中。这样就避免了频繁申请和释放内存的开销,提高了性能。

总结

sync.WaitGroup 是 Go 语言中非常常用的一种工具,用于等待一组 Goroutine 完成执行。除了基本用法外,还有一些高级用法和技巧,如嵌套使用、限流、优雅关闭、单次执行和内存优化等。这些技巧可以帮助我们更好地控制 Goroutine 的执行和等待,提高程序的性能和稳定性。

发布于: 刚刚阅读数: 5
用户头像

Jack

关注

还未添加个人签名 2019-05-12 加入

作为一名技术追求者,我对科技、编程和创新充满热情。我始终关注最新的商业趋势和技术发展,努力将其应用于实践中,从而推动技术创新和改进。我善于思考和分析,具备较强的解决问题的能力和团队合作精神。

评论

发布
暂无评论
sync.WaitGroup:掌握并发编程中的重要工具_Jack_InfoQ写作社区