Golang中怎么實(shí)現(xiàn)并發(fā)控制,針對這個問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
創(chuàng)新互聯(lián)建站成都網(wǎng)站建設(shè)按需策劃,是成都網(wǎng)站推廣公司,為LED顯示屏提供網(wǎng)站建設(shè)服務(wù),有成熟的網(wǎng)站定制合作流程,提供網(wǎng)站定制設(shè)計服務(wù):原型圖制作、網(wǎng)站創(chuàng)意設(shè)計、前端HTML5制作、后臺程序開發(fā)等。成都網(wǎng)站推廣熱線:028-86922220
Golang中通過go關(guān)鍵字就可開啟一個goroutine,因此,在Go中可以輕松寫出并發(fā)代碼。但是,如何對這些并發(fā)執(zhí)行的groutines有效地控制?
提到并發(fā)控制,很多人可能最先想到的是鎖。Golang中同樣提供了鎖的相關(guān)機(jī)制,包括互斥鎖sync.Mutex,和讀寫鎖sync.RWMutex。除了鎖,還有原子操作sync/atomic等。但是,這些機(jī)制關(guān)注的重點(diǎn)是goroutines的并發(fā)數(shù)據(jù)安全性。而本文想討論的是goroutine的并發(fā)行為控制。
在goroutine并發(fā)行為控制中,有三種常見的方式,分別是WaitGroup、channel和Context。
WaitGroup位于sync包下,它的使用方法如下。
func main() {
var wg sync.WaitGroup
wg.Add(2) //添加需要完成的工作量2
go func() {
wg.Done() //完成工作量1
fmt.Println("goroutine 1 完成工作!")
}()
go func() {
wg.Done() //完成工作量1
fmt.Println("goroutine 2 完成工作!")
}()
wg.Wait() //等待工作量2均完成
fmt.Println("所有的goroutine均已完成工作!")
}
輸出:
//goroutine 2 完成工作!
//goroutine 1 完成工作!
//所有的goroutine均已完成工作!
WaitGroup這種并發(fā)控制方式尤其適用于:某任務(wù)需要多 goroutine 協(xié)同工作,每個 goroutine 只能做該任務(wù)的一部分,只有全部的 goroutine 都完成,任務(wù)才算是完成。因此,WaitGroup同名字的含義一樣,是一種等待的方式。
但是,在實(shí)際的業(yè)務(wù)中,有這么一種場景:當(dāng)滿足某個要求時,需主動的通知某一個 goroutine 結(jié)束。比如我們開啟一個后臺監(jiān)控goroutine,當(dāng)不再需要監(jiān)控時,就應(yīng)該通知這個監(jiān)控 goroutine 結(jié)束,不然它會一直空轉(zhuǎn),造成泄漏。
對于上述場景,WaitGroup無能為力。那能想到的最簡單的方法:定義一個全局變量,在其它地方通過修改這個變量進(jìn)行通知,后臺 goroutine 會不停的檢查這個變量,如果發(fā)現(xiàn)變量發(fā)生了變化,即自行關(guān)閉,但是這個方法未免有些笨拙。這種情況,channel+select可派上用場。
func main() {
exit := make(chan bool)
go func() {
for {
select {
case <-exit:
fmt.Println("退出監(jiān)控")
return
default:
fmt.Println("監(jiān)控中")
time.Sleep(2 * time.Second)
}
}
}()
time.Sleep(5 * time.Second)
fmt.Println("通知監(jiān)控退出")
exit <- true
//防止main goroutine過早退出
time.Sleep(5 * time.Second)
}
輸出:
//監(jiān)控中
//監(jiān)控中
//監(jiān)控中
//通知監(jiān)控退出
//退出監(jiān)控
這種 channel+select 的組合,是比較優(yōu)雅的通知goroutine 結(jié)束的方式。
但是,該方案同樣存在局限性。試想,如果有多個 goroutine 都需要控制結(jié)束怎么辦?如果這些 goroutine 又衍生了其它更多的goroutine 呢?當(dāng)然我們可以定義很多 channel 來解決這個問題,但是 goroutine 的關(guān)系鏈導(dǎo)致這種場景的復(fù)雜性。
以上場景常見于CS架構(gòu)模型下。在Go中,常常為每個client開啟單獨(dú)的goroutine(A)來處理它的一系列request,并且往往單個A中也會請求其他服務(wù)(啟動另一個goroutine B),B也可能會請求另外的goroutine C,C再將request發(fā)送給例如Databse的server。設(shè)想,當(dāng)client斷開連接,那么與之相關(guān)聯(lián)的A、B、C均需要立即退出,系統(tǒng)才可回收A、B、C所占用的資源。退出A簡單,但是,如何通知B、C也退出呢?
這個時候,Context就出場了。
func A(ctx context.Context, name string) {
go B(ctx ,name) //A調(diào)用了B
for {
select {
case <-ctx.Done():
fmt.Println(name, "A退出")
return
default:
fmt.Println(name, "A do something")
time.Sleep(2 * time.Second)
}
}
}
func B(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "B退出")
return
default:
fmt.Println(name, "B do something")
time.Sleep(2 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go A(ctx, "【請求1】") //模擬client來了1個連接請求
time.Sleep(3 * time.Second)
fmt.Println("client斷開連接,通知對應(yīng)處理client請求的A,B退出")
cancel() //假設(shè)滿足某條件client斷開了連接,那么就傳播取消信號,ctx.Done()中得到取消信號
time.Sleep(3 * time.Second)
}
輸出:
//【請求1】 A do something
//【請求1】 B do something
//【請求1】 A do something
//【請求1】 B do something
//client斷開連接,通知對應(yīng)處理client請求的A,B退出
//【請求1】 B退出
//【請求1】 A退出
示例中模擬了客戶端來了連接請求,相應(yīng)開啟Goroutine A進(jìn)行處理,A同時開啟了B處理,A和B都使用了 Context 進(jìn)行跟蹤,當(dāng)我們使用 cancel 函數(shù)通知取消時,這 2個 goroutine 都會被結(jié)束。
這就是 Context 的控制能力,它就像一個控制器一樣,按下開關(guān)后,所有基于這個 Context 或者衍生的子 Context 都會收到通知,這時就可以進(jìn)行清理操作了,最終釋放 goroutine,這就優(yōu)雅的解決了 goroutine 啟動后不可控的問題。
關(guān)于Golang中怎么實(shí)現(xiàn)并發(fā)控制問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識。