文章目录
Java中的门闩最基础的实现方式就是CountDownLatch,可以参考之前的文章《使用三个线程,按顺序打印X,Y,Z,连续打印10次》; 而Go中可以通过sync.WaitGroup来实现门闩。
先来看看Go语言标准库文档中对WaitGroup的描述:
【WaitGroup】用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。
【Add】方法向内部计数加上delta,delta可以是负数;如果内部计数器变为0,Wait方法阻塞等待的所有线程都会释放,如果计数器小于0,方法panic。注意Add加上正数的调用应在Wait之前,否则Wait可能只会等待很少的线程。一般来说本方法应在创建新的线程或者其他应等待的事件之前调用。
【Done】方法减少WaitGroup计数器的值,应在线程的最后执行。
【Wait】方法阻塞直到WaitGroup计数器减为0。
通过上面的描述可以发现他和Java的CountDownLatch作用很像。
Add(X)
方法对应new CountDownLatch(X)
,代表设置计数器
Done()
方法对应CountDownLatch.countDown()
,代表计数器减1
Wait()
方法对应CountDownLatch.await()
,代表在这里阻塞住
为了和Java的门闩形成对比,这里用Go的WaitGroup实现“使用三个线程,按顺序打印X,Y,Z,连续打印10次”的功能。
package main
import (
"fmt"
"sync"
)
func main() {
// 打印X的协程门闩
countLatchX := sync.WaitGroup{}
countLatchX.Add(1)
// 打印Y的协程门闩
countLatchY := sync.WaitGroup{}
countLatchY.Add(1)
// 打印Z的协程门闩
countLatchZ := sync.WaitGroup{}
countLatchZ.Add(1)
// 总门闩
countLatchMain := sync.WaitGroup{}
countLatchMain.Add(3)
// 打印X的协程
go func() {
for i:=0; i<10; i++{
// X门闩如果计数>0则阻塞
countLatchX.Wait()
fmt.Println("print-x:X")
// 让Y门闩计数减1,从而让Y协程打印Y
countLatchY.Done()
// 让X协程下一次打印X前阻塞
countLatchX.Add(1)
}
countLatchMain.Done()
}()
// 打印Y的协程
go func() {
for i:=0; i<10; i++{
// Y门闩如果计数>0则阻塞
countLatchY.Wait()
fmt.Println("print-y:Y")
// 让Z门闩计数减1,从而让Z协程打印Z
countLatchZ.Done()
// 让Y协程下一次打印Y前阻塞
countLatchY.Add(1)
}
countLatchMain.Done()
}()
// 打印Z的协程
go func() {
for i:=0; i<10; i++{
// Z门闩如果计数>0则阻塞
countLatchZ.Wait()
fmt.Println("print-z:Z")
fmt.Println("-----------")
// 让X门闩计数减1,从而让X协程打印X
countLatchX.Done()
// 让Z协程下一次打印Z前阻塞
countLatchZ.Add(1)
}
countLatchMain.Done()
}()
// 让门闩X计数减1,从而让X协程先打印X
countLatchX.Done()
// 阻塞等待所有协程打印完毕,这里区别于Java,Java中主线程结束,非后台子线程可以继续执行;Go主线程结束,创建的协程也就结束了
countLatchMain.Wait()
}
执行结果如下:
print-x:X
print-y:Y
print-z:Z
-----------
print-x:X
print-y:Y
print-z:Z
-----------
print-x:X
print-y:Y
print-z:Z
-----------
print-x:X
print-y:Y
print-z:Z
-----------
print-x:X
print-y:Y
print-z:Z
-----------
print-x:X
print-y:Y
print-z:Z
-----------