社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
相信你已经对 goroutine 已经轻车熟路了,既然如此,我们来完成一个小任务。
背景是这样,我们的任务分成 3 个阶段。
特别的,在上面的过程中,每个步骤的耗时都是不一样的。我们希望使用 goroutine 来模拟这个过程。
不妨假设,从 A 采购的 3 种材料分别为 X0, Y0, Z0. 经过 B 加工后,X0 变成了 X,Y0 变成了 Y,Z0 变成了 Z. 最后拿到了 X, Y, Z 后,找到 C 进行组装,得到 XYZ 产品。
我们用伪代码描述上面的过程:
x, y, z := A() // 采购 x, y, z,初始的值是 "X0", "Y0", "Z0"
go B1(&x) // 加工 x
go B2(&y) // 加工 y
go B3(&z) // 加工 z
wait B1, B2, B3 // 等待三个车间全部完成加工
p := C(&x, &y, &z) // 组装
package main
import (
"fmt"
"time"
)
func A() (string, string, string) {
time.Sleep(1 * time.Second)
return "X0", "Y0", "Z0"
}
func B1(x *string) {
time.Sleep(1 * time.Second)
*x = "X"
}
func B2(y *string) {
time.Sleep(2 * time.Second)
*y = "Y"
}
func B3(z *string) {
time.Sleep(3 * time.Second)
*z = "Z"
}
func C(x, y, z string) string {
time.Sleep(1 * time.Second)
return x + y + z
}
func elapse() func() {
now := time.Now()
return func() {
fmt.Printf("elapse:%.3f msn", 1000*time.Since(now).Seconds())
}
}
func main() {
defer elapse()()
x, y, z := A()
go func() {
B1(&x)
}()
go func() {
B2(&y)
}()
go func() {
B3(&z)
}()
p := C(x, y, z)
fmt.Printf("produce:%sn", p) // Output: X0Y0Z0
}
上面这段程序明显是不符合预期的。因为工厂 C 根本没有等待 B 全部完成加工,就进行生产了。聪明的同学应该立即想到了使用 channel 进行同步的方法。
func main() {
defer elapse()()
x, y, z := A()
wait := make(chan struct{}, 3) // 使用大小为 3 的 channel 进行同步
go func() {
B1(&x)
wait <- struct{}{}
}()
go func() {
B2(&y)
wait <- struct{}{}
}()
go func() {
B3(&z)
wait <- struct{}{}
}()
for i := 0; i < 3; i++ {
<-wait
}
p := C(x, y, z)
fmt.Printf("produce:%sn", p)
}
你以为这样就结束了吗?如果只是讲一下利用 channel 进行 goroutine 同步,那确实没什么好讲的。这里我们介绍另一种试进行 goroutine 同步,使用 go 标准库自带的 package,sync 包。
来看一下最终的程序:
func main() {
defer elapse()()
x, y, z := A()
// 你得事先 import "sync"
var wg sync.WaitGroup
wg.Add(3)
go func() {
B1(&x)
wg.Done()
}()
go func() {
B2(&y)
wg.Done()
}()
go func() {
B3(&z)
wg.Done()
}()
wg.Wait()
p := C(x, y, z)
fmt.Printf("produce:%sn", p)
}
这个程序同样可以达到图 2 中那样的效果。接下来我们分析一下,这段程序是怎么工作的,以及 sync.WaitGroup 是干啥使的。
明白了它的原理后,你甚至可以使用 channel 粗糙的实现一个类似 sync.WaitGroup 类似的功能。当然了,这不能做到像 WaitGroup 这样的灵活度,不过这里我们就不深究了。
A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.
WaitGroup 的作用是等待一组协程结束。主协程通过调用 Add 方法来设置要等待的协程数量。每个协程在运行结束后,都需要调用 Done 方法。Wait 方法可以用来阻塞主协程,直到所有的协程结束。
从文档上看,WaitGroup 提供的三个方法 Add, Done, Wait 在我们上面的例子中已经全部用到了,用法也非常简单。
当然了,Sync 包除了 WaitGroup 这样的类型外,还有诸多其它类型,以后我们还会介绍。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!