Golang 同步等待组(WaitGroup)

1,406 阅读2分钟

在实际使用Go协程实现并行应用时,可能会遇到这样场景:需要阻塞部分代码执行,直到其他协程成功执行之后才继续执行。
示例代码:

package main

import "fmt"

func myFunc() {
    fmt.Println("hello my goroutine")
}

func main() {
    fmt.Println("Hello World")
    go myFunc()
    fmt.Println("main goroutine done")}

结果如下:

Hello World
main goroutine done

协程内的信息"hello my goroutine"并没有出现。在程序启动时,Go程序就会为main()函数创建一个默认的goroutine。当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine会一同结束,main函数所在的goroutine就像是权利的游戏中的夜王,其他的goroutine都是异鬼,夜王一死它转化的那些异鬼也就全部GG了。说白了就是因为main在协程执行之前已经结束,所以协程中的逻辑并未执行。

同步等待组(WaitGroups)就是要解决这类问题,阻塞应用直到同步等待组中的所有协程都成功执行。

WatiGroup是sync包中的一个struct类型,用来收集需要等待执行完成的goroutine。

它有3个方法:

  • Add():每次激活想要被等待完成的goroutine之前,先调用Add(),用来设置或添加要等待完成的goroutine数量
    • 例如Add(2)或者两次调用Add(1)都会设置等待计数器的值为2,表示要等待2个goroutine完成
  • Done():每次需要等待的goroutine在真正完成之前,应该调用该方法来人为表示goroutine完成了,该方法会对等待计数器减1
  • Wait():在等待计数器减为0之前,Wait()会一直阻塞当前的goroutine

所以我们代码改一下:

package main

import (
    "fmt"
    "sync"
)

func myFunc(waitgroup *sync.WaitGroup) {
    fmt.Println("hello my goroutine")
    waitgroup.Done()
}

func main() {
    fmt.Println("Hello World")

    var waitgroup sync.WaitGroup
    waitgroup.Add(1)
    go myFunc(&waitgroup)
    waitgroup.Wait()

    fmt.Println("main goroutine done")}

结果如下:

Hello World
hello my goroutine
main goroutine done

还有一点需要特别注意的是myFunc()中使用指针类型的*sync.WaitGroup作为参数,这里不能使用值类型的sync.WaitGroup作为参数,因为这意味着每个goroutine都拷贝一份waitgroup,每个goroutine都使用自己的waitgroup。这显然是不合理的,多个goroutine应该共享一个waitgroup,才能知道这多个goroutine都完成了。实际上,如果使用值类型的参数,main goroutine将会永久阻塞而导致产生死锁。

我们也可以使用匿名函数实现相同功能。对于协程内部业务不复杂,匿名函数会让程序更简洁:

package main

import (
    "fmt"
    "sync"
)

func main() {
    fmt.Println("Hello World")

    var waitgroup sync.WaitGroup
    waitgroup.Add(1)
    go func() {
        fmt.Println("hello my goroutine")
        waitgroup.Done()
    }()
    waitgroup.Wait()

    fmt.Println("main goroutine done")
}

参考文章:blog.csdn.net/neweastsun/…

www.cnblogs.com/f-ck-need-u…