Go 阻塞的實(shí)現(xiàn)示例
阻塞
在Go語(yǔ)言中,阻塞通常指的是一個(gè)goroutine(輕量級(jí)線程)在等待另一個(gè)goroutine完成操作(如I/O操作、channel通信等)時(shí),暫時(shí)停止執(zhí)行的現(xiàn)象。Go語(yǔ)言提供了多種同步和通信機(jī)制,可以用于實(shí)現(xiàn)阻塞的效果。
使用 Channel 實(shí)現(xiàn)阻塞
Channel 是Go語(yǔ)言中的一個(gè)核心特性,用于在goroutines之間進(jìn)行通信。通過(guò)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語(yǔ)言中用于同步一組并發(fā)操作的另一個(gè)工具。它通過(guò)計(jì)數(shù)器來(lái)跟蹤完成的操作數(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都通過(guò)調(diào)用wg.Done()通知它們已經(jīng)完成工作
wg.Wait() // 阻塞,直到WaitGroup的計(jì)數(shù)器為0
fmt.Println("所有業(yè)務(wù)處理完成~~~")
}
使用 Mutex 和 Conditional Variables 實(shí)現(xiàn)阻塞
Mutex(互斥鎖)和條件變量可以用來(lái)同步訪問(wèn)共享資源,并實(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 來(lái)改變條件變量 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)里面阻塞等待完成后也可以,也可以沒(méi)有,因?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()來(lái)釋放互斥鎖,否則主 goroutine 會(huì)在cond.Wait()之后無(wú)法獲取到鎖。您的代碼中,
cond.Wait()之后的mtx.Unlock()應(yīng)該在for循環(huán)之外,以避免在循環(huán)的每次迭代中重復(fù)加鎖和解鎖。
在Go語(yǔ)言中,
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()
- 在訪問(wèn)或修改由互斥鎖保護(hù)的共享資源之前使用。
- 在調(diào)用
cond.Wait()之前使用,以確保在等待條件變量時(shí),共享資源不會(huì)被其他goroutine并發(fā)訪問(wèn)。 - 在調(diào)用
cond.Signal()或cond.Broadcast()之前使用,因?yàn)檫@些操作需要在互斥鎖保護(hù)的臨界區(qū)內(nèi)執(zhí)行。
mtx.Unlock()
- 在完成對(duì)共享資源的訪問(wèn)或修改后使用。
- 在
cond.Wait()返回后使用,因?yàn)槲覀円呀?jīng)完成了等待期間需要的共享資源訪問(wèn),并且需要重新獲取互斥鎖以繼續(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í)終止該程序??梢酝ㄟ^(guò)調(diào)用os.Exit或從main()函數(shù)的返回來(lái)以正常方式終止程序。而有時(shí)候我們需要的是使程序阻塞在這一行。
使用 sync.WaitGroup
一直等待直到 WaitGroup 等于 0
package main
import "sync"
func main() {
var wg sync.WaitGroup
wg.Add(1)
wg.Wait()
}
空 select
select{}是一個(gè)沒(méi)有任何 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ā)送來(lái)請(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),可以通過(guò)終端發(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ā)送來(lái)請(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é)
注意上面寫(xiě)的的代碼大部分不能直接運(yùn)行,都會(huì) panic,提示“all goroutines are asleep - deadlock!”,因?yàn)?go 的 runtime 會(huì)檢查你所有的 goroutine 都卡住了, 沒(méi)有一個(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語(yǔ)言使用HTTP包創(chuàng)建WEB服務(wù)器的方法
這篇文章主要介紹了Go語(yǔ)言使用HTTP包創(chuàng)建WEB服務(wù)器的方法,結(jié)合實(shí)例形式分析了Go語(yǔ)言基于HTTP包創(chuàng)建WEB服務(wù)器客戶端與服務(wù)器端的實(shí)現(xiàn)方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-07-07
Golang使用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開(kāi)發(fā)文件上傳功能時(shí),只需要簡(jiǎn)單幾行代碼便可以實(shí)現(xiàn),需要的朋友可以參考下2024-02-02
Golang應(yīng)用執(zhí)行Shell命令實(shí)戰(zhàn)
本文主要介紹了Golang應(yīng)用執(zhí)行Shell命令實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
一文帶你了解Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)math和rand的常用函數(shù)
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)math和rand中的常用函數(shù),文中的示例代碼講解詳細(xì), 對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,感興趣的小伙伴可以了解一下2022-12-12
Go在GoLand中引用github.com中的第三方包具體步驟
這篇文章主要給大家介紹了關(guān)于Go在GoLand中引用github.com中第三方包的具體步驟,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Go具有一定的參考價(jià)值,需要的朋友可以參考下2024-01-01
Go語(yǔ)言七篇入門(mén)教程三函數(shù)方法及接口
這篇文章主要為大家介紹了Go語(yǔ)言的函數(shù)方法及接口的示例詳解,本文是Go語(yǔ)言七篇入門(mén)系列文章,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11

