Go 阻塞的實(shí)現(xiàn)示例
阻塞
在Go語言中,阻塞通常指的是一個(gè)goroutine(輕量級線程)在等待另一個(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)樗粫?dǎo)致計(jì)數(shù)變?yōu)樨?fù)數(shù)(如果已經(jīng)到達(dá)零,則會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) // 啟動一個(gè)goroutine做工作
}
//主goroutine調(diào)用wg.Wait(),直到所有啟動的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
// 啟動一個(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)橹骶€程會結(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)前的互斥鎖會被自動釋放,goroutine 會阻塞直到它被cond.Signal()或cond.Broadcast()喚醒。一旦
cond.Wait()返回,goroutine 會重新獲取互斥鎖,然后繼續(xù)執(zhí)行循環(huán)或代碼塊。在
cond.Signal()調(diào)用之后,您需要在某個(gè)地方調(diào)用mtx.Unlock()來釋放互斥鎖,否則主 goroutine 會在cond.Wait()之后無法獲取到鎖。您的代碼中,
cond.Wait()之后的mtx.Unlock()應(yīng)該在for循環(huán)之外,以避免在循環(huán)的每次迭代中重復(fù)加鎖和解鎖。
在Go語言中,
sync.Mutex(互斥鎖)用于保護(hù)共享資源不被多個(gè)goroutine同時(shí)修改,以避免競態(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í),共享資源不會被其他goroutine并發(fā)訪問。 - 在調(diào)用
cond.Signal()或cond.Broadcast()之前使用,因?yàn)檫@些操作需要在互斥鎖保護(hù)的臨界區(qū)內(nèi)執(zhí)行。
mtx.Unlock()
- 在完成對共享資源的訪問或修改后使用。
- 在
cond.Wait()返回后使用,因?yàn)槲覀円呀?jīng)完成了等待期間需要的共享資源訪問,并且需要重新獲取互斥鎖以繼續(xù)執(zhí)行。 - 在不再需要互斥鎖保護(hù)當(dāng)前goroutine的執(zhí)行路徑時(shí)使用,以允許其他等待互斥鎖的goroutine繼續(xù)執(zhí)行。
注意事項(xiàng)
- 互斥鎖必須在獲取后及時(shí)釋放,否則會導(dǎo)致死鎖。
- 通常,獲取互斥鎖和釋放互斥鎖成對出現(xiàn),以避免忘記釋放鎖。
永久阻塞
Go 的運(yùn)行時(shí)的當(dāng)前設(shè)計(jì),假定程序員自己負(fù)責(zé)檢測何時(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,它會一直阻塞
package main
func main() {
select{}
}死循環(huán)
雖然能阻塞,但會 100%占用一個(gè) cpu。不建議使用
package main
func main() {
for {}
}用 sync.Mutex
一個(gè)已經(jīng)鎖了的鎖,再鎖一次會一直阻塞,這個(gè)不建議使用
package main
import "sync"
func main() {
var m sync.Mutex
m.Lock()
}os.Signal
系統(tǒng)信號量,在 go 里面也是個(gè) channel,在收到特定的消息之前一直阻塞
package main
import (
"os"
"os/signal"
"syscall"
)
func main() {
sig := make(chan os.Signal, 2)
//syscall.SIGTERM 是默認(rèn)的終止進(jì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)前前臺進(jìn)程發(fā)送一個(gè)SIGINT(中斷)信號。這通常是停止Go程序的快捷方式。Kill命令: 如果你的程序在后臺運(yùn)行,并且你知道其進(jìn)程ID(PID),可以通過終端發(fā)送一個(gè)信號。例如,發(fā)送一個(gè)
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)的終止進(jì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ù)運(yùn)行,可能由如systemd、supervisord等服務(wù)管理器控制,這些管理器通常提供了發(fā)送信號給托管服務(wù)的機(jī)制。具體操作需參考相應(yīng)服務(wù)管理器的文檔。
空 channel 或者 nil channel
channel 會一直阻塞直到收到消息,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)行,都會 panic,提示“all goroutines are asleep - deadlock!”,因?yàn)?go 的 runtime 會檢查你所有的 goroutine 都卡住了, 沒有一個(gè)要執(zhí)行。
你可以在阻塞代碼前面加上一個(gè)或多個(gè)你自己業(yè)務(wù)邏輯的 goroutine,這樣就不會 deadlock 了。
到此這篇關(guān)于Go 阻塞的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Go 阻塞內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang interface判斷為空nil的實(shí)現(xiàn)代碼
這篇文章主要介紹了golang interface判斷為空nil的實(shí)現(xiàn)代碼,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04
在Golang中使用http.FileServer返回靜態(tài)文件的操作
這篇文章主要介紹了在Golang中使用http.FileServer返回靜態(tài)文件的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12
Golang?throttled基于GCRA速率限制庫使用探索
這篇文章主要為大家介紹了Golang?throttled基于GCRA速率限制庫使用實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
利用Go Plugin實(shí)現(xiàn)插件化編程的簡單方法
Golang官方提供了plugin模塊,該模塊可以支持插件開,下面這篇文章主要給大家介紹了關(guān)于如何利用Go Plugin實(shí)現(xiàn)插件化編程的相關(guān)資料,需要的朋友可以參考下2021-10-10
Golang 空map和未初始化map的注意事項(xiàng)說明
這篇文章主要介紹了Golang 空map和未初始化map的注意事項(xiàng)說明,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04

