Go并發(fā)可視化解釋:sync.WaitGroup
場景
Avito是一名校車司機(jī),他幫助4個Gopher孩子上學(xué)。每天,Avito在他們的社區(qū)等待孩子們。他不知道孩子們需要多長時間,但他確切地知道有4個孩子他需要等待。
1*aZnEggopv4Tsbyyj3e5JFg.png
當(dāng)一個孩子準(zhǔn)備好時,他/她會說:Done(),將計(jì)數(shù)器減1。Avito仍然被阻塞,因?yàn)橛?jì)數(shù)器仍然大于0。他必須等到所有其他孩子準(zhǔn)備好。
1*qouGWmMAqY2CDrzz5widhQ.png
如果有兩個孩子同時準(zhǔn)備好,它們的同時準(zhǔn)備會導(dǎo)致WaitGroup出現(xiàn)不一致嗎?絕對不會。與sync包中的大多數(shù)其他組件一樣,WaitGroup具有內(nèi)置的同步機(jī)制,以處理并發(fā)。因此,計(jì)數(shù)器減少了準(zhǔn)備好的孩子數(shù)量。
1*057bX4zo_LCzEkdzGyDYpA.png
在最后一個孩子準(zhǔn)備好后,Avito啟動引擎,將他們送到學(xué)校。
1*rjH8OR3t7QgUx-dO-Iszeg.png
就是這樣!正如我所說,sync.WaitGroup很簡單。
超時
如果一個孩子花費(fèi)太多時間準(zhǔn)備,他們會不會因此遲到?如果Avito在時間到達(dá)時不管怎樣都開始行駛會更好嗎?嗯,Golang傾向于保持一切盡可能簡潔,因此與其他編程語言中的CountDownLatch(例如Java中的)不同,sync.WaitGroup默認(rèn)情況下不支持超時。在這種情況下,選擇語句可能會有所幫助。
func main() {
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(5 * time.Second)
}()
done := make(chan bool)
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
log.Println("All done")
case <-time.After(1 * time.Second):
log.Println("Hit timeout")
}
}
孩子等待
在上面的示例中,Avito(主Goroutine)等待孩子(子Goroutines)。當(dāng)我們希望子Goroutines等待主Goroutine時,WaitGroup也可以使用。想象一下孩子們正在進(jìn)行體育課。Torcher - 體育老師,在學(xué)生中主持比賽。他向WaitGroup中Add(1),并要求所有孩子在相同的WaitGroup上Wait()。
1*btDQK4QKsu1HkEpfJDa2EA.png
當(dāng)Torcher調(diào)用wg.Done()時,計(jì)數(shù)器變?yōu)?,允許所有孩子同時開始奔跑。
1*VkV3VlRTx5jxXhauBH0_Dg.png
展示你的代碼!
package main
import (
"log"
"sync"
"time"
)
func main() {
kids := []string{"Partier", "Stringer", "Candier", "Swimmer"}
wg := sync.WaitGroup{}
wg.Add(len(kids))
for _, kid := range kids {
go func(name string) {
defer wg.Done()
prepare(name)
}(kid)
}
log.Printf("Avito: I'm waiting for %d kids\n", len(kids))
wg.Wait()
log.Println("Avito: The kids are all ready, go!")
}
func prepare(name string) {
log.Printf("%v: I'm preparing for school\n", name)
time.Sleep(2 * time.Second)
log.Printf("%v: I'm ready\n", name)
}