Go 阻塞的實(shí)現(xiàn)示例
阻塞
在Go語言中,阻塞通常指的是一個(gè)goroutine(輕量級(jí)線程)在等待另一個(gè)goroutine完成操作(如I/O操作、channel通信等)時(shí),暫時(shí)停止執(zhí)行的現(xiàn)象。Go語言提供了多種同步和通信機(jī)制,可以用于實(shí)現(xiàn)阻塞的效果。
使用 Channel 實(shí)現(xiàn)阻塞
Channel 是Go語言中的一個(gè)核心特性,用于在goroutines之間進(jìn)行通信。通過channel,你可以實(shí)現(xiàn)阻塞等待數(shù)據(jù)或命令。
package main import ( "fmt" "time" ) func main() { c := make(chan struct{}) go func() { fmt.Println("業(yè)務(wù)處理~~~") time.Sleep(2 * time.Second) fmt.Println("業(yè)務(wù)處理完成~~~") close(c) // 關(guān)閉channel,通知工作完成 }() <-c // 阻塞等待channel關(guān)閉 fmt.Println("處理其他業(yè)務(wù)~~~") }
使用 WaitGroup 實(shí)現(xiàn)阻塞
WaitGroup 是Go語言中用于同步一組并發(fā)操作的另一個(gè)工具。它通過計(jì)數(shù)器來跟蹤完成的操作數(shù)量。
package main import ( "fmt" "strconv" "sync" "time" ) func main() { var wg sync.WaitGroup //控制并發(fā)組 doWork := func(i int) { // wg.Done(): 表示一個(gè)事件已經(jīng)完成。它等價(jià)于 wg.Add(-1),但更明確地表達(dá)了“完成一個(gè)任務(wù)”的意圖,并且在使用上更安全,因?yàn)樗粫?huì)導(dǎo)致計(jì)數(shù)變?yōu)樨?fù)數(shù)(如果已經(jīng)到達(dá)零,則會(huì)panic)。 defer wg.Done() // 當(dāng)函數(shù)返回時(shí),通知WaitGroup一個(gè)操作已完成相當(dāng)于wg.Add(-1) fmt.Println("處理業(yè)務(wù)~~~" + strconv.Itoa(i)) time.Sleep(2 * time.Second) fmt.Println("業(yè)務(wù)處理完成~~~" + strconv.Itoa(i)) } for i := 0; i < 5; i++ { wg.Add(1) // 增加WaitGroup的計(jì)數(shù)器 go doWork(i) // 啟動(dòng)一個(gè)goroutine做工作 } //主goroutine調(diào)用wg.Wait(),直到所有啟動(dòng)的goroutines都通過調(diào)用wg.Done()通知它們已經(jīng)完成工作 wg.Wait() // 阻塞,直到WaitGroup的計(jì)數(shù)器為0 fmt.Println("所有業(yè)務(wù)處理完成~~~") }
使用 Mutex 和 Conditional Variables 實(shí)現(xiàn)阻塞
Mutex(互斥鎖)和條件變量可以用來同步訪問共享資源,并實(shí)現(xiàn)基于條件的阻塞。
package main import ( "fmt" "sync" "time" ) func main() { var mtx sync.Mutex //創(chuàng)建互斥鎖 cond := sync.NewCond(&mtx) //使用mtx作為底層互斥鎖 ready := false // 啟動(dòng)一個(gè) goroutine 來改變條件變量 ready 的值,并通知 cond。 go func() { fmt.Println("循環(huán)跟goroutine是go內(nèi)部決定先調(diào)度的--------------------goroutine--------------------") time.Sleep(3 * time.Second) mtx.Lock() //使用互斥鎖 ready = true cond.Signal() // 喚醒至少一個(gè)等待的 goroutine mtx.Unlock() //解鎖 }() mtx.Lock() // 鎖定互斥鎖,準(zhǔn)備進(jìn)入條件等待 for !ready { fmt.Println("循環(huán)跟goroutine是go內(nèi)部決定先調(diào)度的--------------------阻塞--------------------") cond.Wait() // 阻塞,直到 cond.Signal() 被調(diào)用 //mtx.Unlock() } mtx.Unlock() // 解鎖互斥鎖,繼續(xù)執(zhí)行(此處mtx.Unlock()在for循環(huán)里面阻塞等待完成后也可以,也可以沒有,因?yàn)橹骶€程會(huì)結(jié)束,但如果后續(xù)還需要獲取互斥鎖則必須要釋放否則報(bào)錯(cuò)) fmt.Println("準(zhǔn)備繼續(xù)~~~") }
這里是一些關(guān)鍵的修改和注意事項(xiàng):
sync.Cond
的使用需要一個(gè)sync.Mutex
作為其底層的互斥鎖。在使用cond.Wait()
之前,必須先鎖定這個(gè)互斥鎖。在
cond.Wait()
調(diào)用中,當(dāng)前的互斥鎖會(huì)被自動(dòng)釋放,goroutine 會(huì)阻塞直到它被cond.Signal()
或cond.Broadcast()
喚醒。一旦
cond.Wait()
返回,goroutine 會(huì)重新獲取互斥鎖,然后繼續(xù)執(zhí)行循環(huán)或代碼塊。在
cond.Signal()
調(diào)用之后,您需要在某個(gè)地方調(diào)用mtx.Unlock()
來釋放互斥鎖,否則主 goroutine 會(huì)在cond.Wait()
之后無法獲取到鎖。您的代碼中,
cond.Wait()
之后的mtx.Unlock()
應(yīng)該在for
循環(huán)之外,以避免在循環(huán)的每次迭代中重復(fù)加鎖和解鎖。
在Go語言中,
sync.Mutex
(互斥鎖)用于保護(hù)共享資源不被多個(gè)goroutine同時(shí)修改,以避免競(jìng)態(tài)條件。sync.Cond
(條件變量)與互斥鎖結(jié)合使用,可以在多個(gè)goroutine之間同步共享?xiàng)l件。以下是關(guān)于何時(shí)使用mtx.Lock()
和mtx.Unlock()
的指導(dǎo):
mtx.Lock()
- 在訪問或修改由互斥鎖保護(hù)的共享資源之前使用。
- 在調(diào)用
cond.Wait()
之前使用,以確保在等待條件變量時(shí),共享資源不會(huì)被其他goroutine并發(fā)訪問。 - 在調(diào)用
cond.Signal()
或cond.Broadcast()
之前使用,因?yàn)檫@些操作需要在互斥鎖保護(hù)的臨界區(qū)內(nèi)執(zhí)行。
mtx.Unlock()
- 在完成對(duì)共享資源的訪問或修改后使用。
- 在
cond.Wait()
返回后使用,因?yàn)槲覀円呀?jīng)完成了等待期間需要的共享資源訪問,并且需要重新獲取互斥鎖以繼續(xù)執(zhí)行。 - 在不再需要互斥鎖保護(hù)當(dāng)前goroutine的執(zhí)行路徑時(shí)使用,以允許其他等待互斥鎖的goroutine繼續(xù)執(zhí)行。
注意事項(xiàng)
- 互斥鎖必須在獲取后及時(shí)釋放,否則會(huì)導(dǎo)致死鎖。
- 通常,獲取互斥鎖和釋放互斥鎖成對(duì)出現(xiàn),以避免忘記釋放鎖。
永久阻塞
Go 的運(yùn)行時(shí)的當(dāng)前設(shè)計(jì),假定程序員自己負(fù)責(zé)檢測(cè)何時(shí)終止一個(gè)
goroutine
以及何時(shí)終止該程序。可以通過調(diào)用os.Exit
或從main()
函數(shù)的返回來以正常方式終止程序。而有時(shí)候我們需要的是使程序阻塞在這一行。
使用 sync.WaitGroup
一直等待直到 WaitGroup
等于 0
package main import "sync" func main() { var wg sync.WaitGroup wg.Add(1) wg.Wait() }
空 select
select{}
是一個(gè)沒有任何 case
的 select
,它會(huì)一直阻塞
package main func main() { select{} }
死循環(huán)
雖然能阻塞,但會(huì) 100%占用一個(gè) cpu。不建議使用
package main func main() { for {} }
用 sync.Mutex
一個(gè)已經(jīng)鎖了的鎖,再鎖一次會(huì)一直阻塞,這個(gè)不建議使用
package main import "sync" func main() { var m sync.Mutex m.Lock() }
os.Signal
系統(tǒng)信號(hào)量,在 go 里面也是個(gè) channel
,在收到特定的消息之前一直阻塞
package main import ( "os" "os/signal" "syscall" ) func main() { sig := make(chan os.Signal, 2) //syscall.SIGTERM 是默認(rèn)的終止進(jìn)程信號(hào),通常由服務(wù)管理器(如systemd、supervisor等)發(fā)送來請(qǐng)求程序正常終止。 //syscall.SIGINT 是中斷信號(hào),一般由用戶按下Ctrl+C鍵觸發(fā),用于請(qǐng)求程序中斷執(zhí)行 signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT) <-sig }
從終端發(fā)送信號(hào)
Ctrl+C: 在大多數(shù)Unix-like系統(tǒng)(包括Linux和macOS)以及Windows的命令行中,按
Ctrl+C
鍵會(huì)向當(dāng)前前臺(tái)進(jìn)程發(fā)送一個(gè)SIGINT
(中斷)信號(hào)。這通常是停止Go程序的快捷方式。Kill命令: 如果你的程序在后臺(tái)運(yùn)行,并且你知道其進(jìn)程ID(PID),可以通過終端發(fā)送一個(gè)信號(hào)。例如,發(fā)送一個(gè)
SIGTERM
信號(hào),可以使用:kill PID或者指定型號(hào)類型kill -SIGTERM PID
從Go代碼內(nèi)部發(fā)送信號(hào)
package main import ( "os" "os/signal" "syscall" "time" ) func main() { sig := make(chan os.Signal, 2) //syscall.SIGTERM 是默認(rèn)的終止進(jìn)程信號(hào),通常由服務(wù)管理器(如systemd、supervisor等)發(fā)送來請(qǐng)求程序正常終止。 //syscall.SIGINT 是中斷信號(hào),一般由用戶按下Ctrl+C鍵觸發(fā),用于請(qǐng)求程序中斷執(zhí)行 signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT) go func() { time.Sleep(10 * time.Second) sig <- syscall.SIGTERM }() go func() { time.Sleep(5 * time.Second) sig <- syscall.SIGINT }() <-sig }
使用外部工具或服務(wù)管理器
如果你的Go程序作為服務(wù)運(yùn)行,可能由如systemd、supervisord等服務(wù)管理器控制,這些管理器通常提供了發(fā)送信號(hào)給托管服務(wù)的機(jī)制。具體操作需參考相應(yīng)服務(wù)管理器的文檔。
空 channel 或者 nil channel
channel
會(huì)一直阻塞直到收到消息,nil channel
永遠(yuǎn)阻塞。
package main func main() { c := make(chan struct{}) <-c }
package main func main() { var c chan struct{} //nil channel <-c }
總結(jié)
注意上面寫的的代碼大部分不能直接運(yùn)行,都會(huì) panic
,提示“all goroutines are asleep - deadlock!”,因?yàn)?go 的 runtime
會(huì)檢查你所有的 goroutine
都卡住了, 沒有一個(gè)要執(zhí)行。
你可以在阻塞代碼前面加上一個(gè)或多個(gè)你自己業(yè)務(wù)邏輯的 goroutine
,這樣就不會(huì) deadlock
了。
到此這篇關(guān)于Go 阻塞的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Go 阻塞內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言使用HTTP包創(chuàng)建WEB服務(wù)器的方法
這篇文章主要介紹了Go語言使用HTTP包創(chuàng)建WEB服務(wù)器的方法,結(jié)合實(shí)例形式分析了Go語言基于HTTP包創(chuàng)建WEB服務(wù)器客戶端與服務(wù)器端的實(shí)現(xiàn)方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-07-07Golang使用Gin實(shí)現(xiàn)文件上傳的示例代碼
本文我們主要介紹了Golang如何使用Gin實(shí)現(xiàn)文件上傳,Go標(biāo)準(zhǔn)庫(kù)net/http對(duì)文件上傳已經(jīng)提供了非常完善的支持,而Gin框架在其基礎(chǔ)上進(jìn)一步封裝,因此使用Gin開發(fā)文件上傳功能時(shí),只需要簡(jiǎn)單幾行代碼便可以實(shí)現(xiàn),需要的朋友可以參考下2024-02-02Golang應(yīng)用執(zhí)行Shell命令實(shí)戰(zhàn)
本文主要介紹了Golang應(yīng)用執(zhí)行Shell命令實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03一文帶你了解Go語言標(biāo)準(zhǔn)庫(kù)math和rand的常用函數(shù)
這篇文章主要為大家詳細(xì)介紹了Go語言標(biāo)準(zhǔn)庫(kù)math和rand中的常用函數(shù),文中的示例代碼講解詳細(xì), 對(duì)我們學(xué)習(xí)Go語言有一定的幫助,感興趣的小伙伴可以了解一下2022-12-12Go在GoLand中引用github.com中的第三方包具體步驟
這篇文章主要給大家介紹了關(guān)于Go在GoLand中引用github.com中第三方包的具體步驟,文中通過圖文介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Go具有一定的參考價(jià)值,需要的朋友可以參考下2024-01-01