GO语言并发编--sync包之WaitGroup的使用

概述

WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。
但在使用时,也有一些问题需要注意,请看本文的详细分解。
另外,WaitGroup的使用场景十分有限,为什么呢?具体原因,请看本文的总结部分的分析。

主要函数

func (*WaitGroup) Add

func (wg *WaitGroup) Add(delta int)

Add方法向内部计数加上delta,delta可以是负数;如果内部计数器变为0,Wait方法阻塞等待的所有协程都会释放,如果计数器小于0,则调用panic。注意Add加上正数的调用应在Wait之前,否则Wait可能只会等待很少的协程。一般来说本方法应在创建新的协程或者其他应等待的事件之前调用。

func (wg *WaitGroup) Done()

func (wg *WaitGroup) Done()

Done方法减少WaitGroup计数器的值,应在协程的最后执行。

func (wg *WaitGroup) Wait()

func (wg *WaitGroup) Wait() 

Wait方法阻塞直到WaitGroup计数器减为0。

编程实战

等待某个协程结束

func main() {
    var wg sync.WaitGroup

    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Println("1 goroutine sleep ...")
        time.Sleep(2e9)
        fmt.Println("1 goroutine exit ...")
    }()

    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Println("2 goroutine sleep ...")
        time.Sleep(4e9)
        fmt.Println("2 goroutine exit ...")
    }()

    fmt.Println("waiting for all goroutine ")
    wg.Wait()
    fmt.Println("All goroutines finished!")
}

小结

要注意Add和Done函数一定要配对,否则可能发生死锁,所报的错误信息如下:

fatal error: all goroutines are asleep - deadlock!

等待协程组结束

func main() {
    sayHello := func(wg *sync.WaitGroup, id int) {
        defer wg.Done()
        fmt.Printf("%v goroutine start ...\n", id)
        time.Sleep(2)
        fmt.Printf("%v goroutine exit ...\n", id)
    }

    var wg sync.WaitGroup
    const N = 5
    wg.Add(N)
    for i := 0; i < N; i++ {
        go sayHello(&wg, i)
    }

    fmt.Println("waiting for all goroutine ")
    wg.Wait()
    fmt.Println("All goroutines finished!")
}

运行以上程序,输出如下:

waiting for all goroutine 
1 goroutine start ...
5 goroutine start ...
5 goroutine exit ...
4 goroutine start ...
4 goroutine exit ...
3 goroutine start ...
1 goroutine exit ...
0 goroutine start ...
3 goroutine exit ...
0 goroutine exit ...
2 goroutine start ...
2 goroutine exit ...
All goroutines finished!

无论运行多少次,都能保证All goroutines finished!这一句在最后一行输出,这说明,Wait()函数等了所有协程都结束自己才返回。

小结

以上程序通过WaitGroup提供的三个同步接口,实现了等待一个协程组完成的同步操作。在实现时要注意:
* Add的参数N必须和创建的goroutine的数量相等,否则会报出死锁的错误信息
* 另外,sayHello()函数中要传递WaitGroup的指针呢?在该结构的实现源码中已经有讲解:

A WaitGroup must not be copied after first use.

就是说,该结构定义后就不能被复制,所以这里要使用指针。

总结

通过WaitGroup提供的三个函数:Add,Done,Wait,可以轻松实现等待某个协程或协程组完成的同步操作。但在使用时要注意:

  • Add的数量和Done的调用数量必须相等。
  • 另外,就是WaitGroup结构一旦定义就不能复制的原因。

WaitGroup在需要等待多个任务结束再返回的业务来说还是很有用的,但现实中用的更多的可能是,先等待一个协程组,若所有协程组都正确完成,则一直等到所有协程组结束;若其中有一个协程发生错误,则告诉协程组的其他协程,全部停止运行(本次任务失败)以免浪费系统资源。
该场景WaitGroup是无法实现的,那么该场景该如何实现呢,就需要用到通知机制,其实也可以用channel来实现,具体的解决办法,请看后续的文章。
这样说来,WaitGroup的使用场景是有限的。

扫描二维码关注公众号,回复: 2883239 查看本文章

猜你喜欢

转载自blog.csdn.net/zg_hover/article/details/81194445