在 Go 語(yǔ)言中管理 Concurrency 的三種方式
相信大家踏入 Go 語(yǔ)言的世界,肯定是被強(qiáng)大的并發(fā)(Concurrency)所吸引,Go 語(yǔ)言用最簡(jiǎn)單的關(guān)鍵字go就可以將任務(wù)丟到后臺(tái)處理,但是開發(fā)者怎么有效率的控制并發(fā),這是入門 Go 語(yǔ)言必學(xué)的技能,本章會(huì)介紹幾種方式來(lái)帶大家認(rèn)識(shí)并發(fā),而這三種方式分別對(duì)應(yīng)到三個(gè)不同的名詞:WaitGroup,Channel,及 Context。下面用簡(jiǎn)單的范例帶大家了解。
WaitGroup
先來(lái)了解有什么情境需要使用到 WaitGroup,假設(shè)您有兩臺(tái)機(jī)器需要同時(shí)上傳最新的代碼,兩臺(tái)機(jī)器分別上傳完成后,才能執(zhí)行最后的重啟步驟。就像是把一個(gè)工作同時(shí)拆成好幾份同時(shí)一起做,可以減少時(shí)間,但是最后需要等到全部做完,才能執(zhí)行下一步,這時(shí)候就需要用到 WaitGroup 才能做到。
package?main
import?(
????"fmt"
????"sync"
)
func?main()?{
????var?wg?sync.WaitGroup
????i?:=?0
????wg.Add(3)?//task?count?wait?to?do
????go?func()?{
????????defer?wg.Done()?//?finish?task1
????????fmt.Println("goroutine?1?done")
????????i++
????}()
????go?func()?{
????????defer?wg.Done()?//?finish?task2
????????fmt.Println("goroutine?2?done")
????????i++
????}()
????go?func()?{
????????defer?wg.Done()?//?finish?task3
????????fmt.Println("goroutine?3?done")
????????i++
????}()
????wg.Wait()?//?wait?for?tasks?to?be?done
????fmt.Println("all?goroutine?done")
????fmt.Println(i)
}
Channel
另外一種實(shí)際的案例就是,我們需要主動(dòng)通知一個(gè) Goroutine 進(jìn)行停止的動(dòng)作。換句話說(shuō),當(dāng) App 啟動(dòng)時(shí),會(huì)在后臺(tái)跑一些監(jiān)控程序,而當(dāng)整個(gè) App 需要停止前,需要發(fā)個(gè) Notification 給后臺(tái)的監(jiān)控程序,將其先停止,這時(shí)候就需要用到 Channel 來(lái)通知??聪孪旅孢@個(gè)例子:
package?main
import?(
????"fmt"
????"time"
)
func?main()?{
????exit?:=?make(chan?bool)
????go?func()?{
????????for?{
????????????select?{
????????????case?<-exit:
????????????????fmt.Println("Exit")
????????????????return
????????????case?<-time.After(2?*?time.Second):
????????????????fmt.Println("Monitoring")
????????????}
????????}
????}()
????time.Sleep(5?*?time.Second)
????fmt.Println("Notify?Exit")
????exit?<-?true?//keep?main?goroutine?alive
????time.Sleep(5?*?time.Second)
}
上面的例子可以發(fā)現(xiàn),用了一個(gè) Gogourtine 和 Channel 來(lái)控制。可以想像當(dāng)后臺(tái)有無(wú)數(shù)個(gè) Goroutine 的時(shí)候,我們就需要用多個(gè) Channel 才能進(jìn)行控制,也許 Goroutine 內(nèi)又會(huì)產(chǎn)生 Goroutine,開發(fā)者這時(shí)候就會(huì)發(fā)現(xiàn)已經(jīng)無(wú)法單純使用 Channel 來(lái)控制多個(gè) Goroutine 了。這時(shí)候解決方式會(huì)是傳遞 Context。
Context
大家可以想像,今天有一個(gè)后臺(tái)任務(wù) A,A 任務(wù)又產(chǎn)生了 B 任務(wù),B 任務(wù)又產(chǎn)生了 C 任務(wù),也就是可以按照此模式一直產(chǎn)生下去,假設(shè)中途我們需要停止 A 任務(wù),而 A 又必須告訴 B 及 C 要一起停止,這時(shí)候通過 context 方式是最快的了。
package?main
import?(
????"context"
????"fmt"
????"time"
)
func?foo(ctx?context.Context,?name?string)?{
????go?bar(ctx,?name)?//?A?calls?B
????for?{
????????select?{
????????case?<-ctx.Done():
????????????fmt.Println(name,?"A?Exit")
????????????return
????????case?<-time.After(1?*?time.Second):
????????????fmt.Println(name,?"A?do?something")
????????}
????}
}
func?bar(ctx?context.Context,?name?string)?{
????for?{
????????select?{
????????case?<-ctx.Done():
????????????fmt.Println(name,?"B?Exit")
????????????return
????????case?<-time.After(2?*?time.Second):
????????????fmt.Println(name,?"B?do?something")
????????}
????}
}
func?main()?{
????ctx,?cancel?:=?context.WithCancel(context.Background())
????go?foo(ctx,?"FooBar")
????fmt.Println("client?release?connection,?need?to?notify?A,?B?exit")
????time.Sleep(5?*?time.Second)
????cancel()?//mock?client?exit,?and?pass?the?signal,?ctx.Done()?gets?the?signal??time.Sleep(3?*?time.Second)
????time.Sleep(3?*?time.Second)
}
大家可以把 context 想成是一個(gè) controller,可以隨時(shí)控制不確定個(gè)數(shù)的 Goroutine,由上往下,只要宣告context.WithCancel后,再任意時(shí)間點(diǎn)都可以通過cancel()來(lái)停止整個(gè)后臺(tái)服務(wù)。實(shí)際案例會(huì)用在當(dāng) App 需要重新啟動(dòng)時(shí),要先通知全部 goroutine 停止,正常停止后,才會(huì)重新啟動(dòng) App。
總結(jié)
根據(jù)不同的情境跟狀況來(lái)選擇不同的方式,做一個(gè)總結(jié):
WaitGroup:需要將單一個(gè)工作分解成多個(gè)子任務(wù),等到全部完成后,才能進(jìn)行下一步,這時(shí)候用 WaitGroup 最適合了 Channel + Select:Channel 只能用在比較單純的 Goroutine 情況下,如果要管理多個(gè) Goroutine,建議還是 走 context 會(huì)比較適合 Context:如果您想一次控制全部的 Goroutine,相信用 context 會(huì)是最適合不過的,當(dāng)然 context 不只有這特性,詳細(xì)可以參考『用 10 分鐘了解 Go 語(yǔ)言 context package 使用場(chǎng)景及介紹』
作者:AppleBOY
原文鏈接:https://blog.wu-boy.com/2020/08/three-ways-to-manage-concurrency-in-go/
推薦閱讀
站長(zhǎng) polarisxu
自己的原創(chuàng)文章
不限于 Go 技術(shù)
職場(chǎng)和創(chuàng)業(yè)經(jīng)驗(yàn)
Go語(yǔ)言中文網(wǎng)
每天為你
分享 Go 知識(shí)
Go愛好者值得關(guān)注
