Fork-Join 模型
在并发中,Fork-Join 模型是一种设置和执行并发程序的方式,使得执行在指定点处分支,并在后续点处加入或合并,并恢复顺序执行。 Fork 一词指的是程序运行时的任何时间点,它可以创建一个或多个子执行分支,以同时运行或在某些情况下与父级并行运行。 Join 一词指的是将来并发执行分支连接回父级的时间点。
WaitGroups
现在,我们对并发模型 Fork-Join 有了更多的了解。看一段代码:
package main
import "fmt"
func sayGreetings() {
fmt.Println("Hello World!!")
}
func main() {
go sayGreetings()
}
当我们运行上面的代码时,会有两种可能。首先,sayGreetings 协程打印 Hello World!并在主 goroutine 之前完成它的执行。在这种情况下,Fork 和 Join 操作都会发生。在第二种情况下,sayGreetings goroutine 将无法在 main goroutine 之前完成其执行。在这种情况下,Join 操作将不会发生。
为了解决上述问题,我们需要确保 Join 操作在所有情况下都发生在主 goroutine 完成执行之前。我们可以使用 Go 内置库的 sync 包提供的同步原语 WaitGroup。
WaitGroup 等待一组 goroutine 完成。我们可以将 WaitGroup 视为并发安全计数器。 WaitGroup 提供了三个附加的方法。
type WaitGroup
func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()
-
func (wg *WaitGroup) Add(delta int)
方法将计数器增加传入的整数。传入 Add 方法的整数表示创建的新 goroutine 的数量。 -
func (wg *WaitGroup) Done()
方法将计数器减一。当 goroutine 完成执行时,我们调用 Done 方法。 -
func (wg *WaitGroup) Wait()
方法用于阻塞执行,直到所有部署的 goroutine 都已完成执行。
使用 sync.WaitGroup
我们可以避免使用 time.Sleep
来等待我们的 goroutines 完成。 相反,我们创建一个 sync.WaitGroup
并为我们希望启动的每个 goroutine 将其计数器加 1。 然后,在每个 goroutine 中,我们递减计数器。 最后我们调用 WaitGroup 上的 Wait()
方法来等待我们所有的 goroutine 完成。 让我们修改之前的示例以使用 sync.WaitGroup
:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("hello, world 1")
}()
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("hello, world 2")
}()
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("hello, world 3")
}()
wg.Wait()
}
当我们运行这段代码时,我们会得到与使用 time.Sleep
时相同或相似的输出,运行该程序:
hello, world 3
hello, world 2
hello, world 1