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