Go中sync?包Cond使用場(chǎng)景分析
背景
編寫(xiě)代碼過(guò)程中, 通常有主協(xié)程和多個(gè)子協(xié)程進(jìn)行協(xié)作的過(guò)程,比如通過(guò) WaitGroup 可以實(shí)現(xiàn)當(dāng)所有子協(xié)程完成之后, 主協(xié)程再繼續(xù)執(zhí)行, 具體可參考:Go 中g(shù)oroutine和WaitGroup的使用
如上的場(chǎng)景是主協(xié)程等待子協(xié)程達(dá)到某個(gè)狀態(tài)再繼續(xù)運(yùn)行。 但是反過(guò)來(lái)怎么操作呢,要求一組子協(xié)程等待主協(xié)達(dá)到某個(gè)狀態(tài)時(shí)才繼續(xù)運(yùn)行。這個(gè)時(shí)候就需要用到 Cond 了
Cond 簡(jiǎn)介
Cond 是和某個(gè)條件相關(guān),在條件還沒(méi)有滿足的時(shí)候,所有等待這個(gè)條件的協(xié)程都會(huì)被阻塞住,只有這個(gè)條件滿足的時(shí)候,等待的協(xié)程才可能繼續(xù)進(jìn)行下去。
Cond 在初始化的時(shí)候,需要關(guān)聯(lián)一個(gè) Locker 接口的實(shí)例,一般會(huì)使用 Mutex 或者 RWMutex。
Cond 關(guān)聯(lián)的 Locker 實(shí)例可以通過(guò) c.L 訪問(wèn),它內(nèi)部維護(hù)著一個(gè)先入先出的等待隊(duì)列。
Cond 分別有三個(gè)方法
- Wait
會(huì)把當(dāng)前協(xié)程放入Cond的等待隊(duì)列中并阻塞,直到被Signal或者Broadcast方法從等待隊(duì)列中移除并喚醒,用于子協(xié)程阻塞。
- Signal
主協(xié)程喚醒等待隊(duì)列中的一個(gè)子協(xié)程,先喚醒最先阻塞的子協(xié)程,被喚醒的子協(xié)程繼續(xù)執(zhí)行。
- Broadcast
主協(xié)程喚醒等待隊(duì)列中的全部協(xié)程,所有子協(xié)程繼續(xù)執(zhí)行。
注意:調(diào)用Signal和Broadcast方法,不強(qiáng)求持有c.L的鎖,調(diào)用Wait方法是必須要持有c.L的鎖。
使用示例
Signal的使用場(chǎng)景
大家都去醫(yī)院先排隊(duì),然后等待叫號(hào),先排隊(duì)的先叫號(hào)。這次模擬有5個(gè)病人,分別先排隊(duì)。 然后護(hù)士根據(jù)排隊(duì)先后來(lái)叫號(hào);
具體場(chǎng)景是,5個(gè)病人在三秒中之內(nèi)分別排號(hào),護(hù)士今天要叫5個(gè)號(hào),一秒叫一個(gè),叫完5個(gè)號(hào)就結(jié)束了
代碼如下:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func main() {
c := sync.NewCond(&sync.Mutex{})
num := 0
// 當(dāng)前叫號(hào)是幾號(hào)
hand_num := 0
for i := 0; i < 5; i++ {
go func(i int) {
// 分別在不同時(shí)間排隊(duì)
time.Sleep(time.Second * time.Duration(rand.Int63n(10)))
c.L.Lock()
num++
// 當(dāng)前取得號(hào)。
cur := num
fmt.Printf("%s %d 號(hào)病人取到了 %d 號(hào)\n", time.Now().Format("2006-01-02 15:04:05"), i, cur)
// 取到號(hào)了,等待叫號(hào)
c.Wait()
fmt.Printf("%s %d 號(hào)病人排隊(duì)號(hào)是 %d 號(hào),被叫號(hào)了\n", time.Now().Format("2006-01-02 15:04:05"), i, cur)
hand_num = cur
c.L.Unlock()
}(i)
}
// 都叫號(hào)了
for hand_num != 5 {
// 叫號(hào)
c.Signal()
time.Sleep(time.Second * 1)
}
time.Sleep(time.Second * 10)
}
執(zhí)行結(jié)果如下

結(jié)果表明,5個(gè)病人,分別在三秒鐘內(nèi)先后取號(hào), 然后護(hù)士每過(guò)一秒鐘按照排隊(duì)的先后順序叫一個(gè)號(hào)(叫號(hào)的過(guò)程依然有病人取號(hào)),先取號(hào)的被先叫號(hào)。
此場(chǎng)景中,5個(gè)病人相當(dāng)于5個(gè)協(xié)程, 主協(xié)程反復(fù)使用Signal() 按照順序一個(gè)個(gè)喚醒阻塞的子協(xié)程。
Broadcast的使用場(chǎng)景
場(chǎng)景為如下: 運(yùn)動(dòng)員跑步比賽,要求8秒內(nèi)全部運(yùn)動(dòng)員準(zhǔn)備好,然后等待教練發(fā)令, 教練10秒后發(fā)令,所有運(yùn)動(dòng)員在發(fā)令后開(kāi)始跑。
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func main() {
c := sync.NewCond(&sync.Mutex{})
for i := 0; i < 10; i++ {
go func(i int) {
// 隨機(jī)一個(gè)8秒內(nèi)的準(zhǔn)備時(shí)間
time.Sleep(time.Second * time.Duration(rand.Int63n(8)))
fmt.Printf("%s 運(yùn)動(dòng)員%d已準(zhǔn)備就緒\n", time.Now().Format("2006-01-02 15:04:05"), i)
c.L.Lock()
// 準(zhǔn)備完畢,等待教練發(fā)令
c.Wait()
c.L.Unlock()
fmt.Printf("%s 運(yùn)動(dòng)員%d開(kāi)跑\n", time.Now().Format("2006-01-02 15:04:05"), i)
}(i)
}
// 主協(xié)程等待10秒后發(fā)令
time.Sleep(time.Second * 10)
fmt.Printf("%s 教練發(fā)令。\n", time.Now().Format("2006-01-02 15:04:05"))
// 教練發(fā)令。通知所有運(yùn)動(dòng)員開(kāi)始跑步, 即喚起之前 wait()的所有協(xié)程
c.Broadcast()
// 等待跑步
time.Sleep(time.Second * 5)
}
執(zhí)行結(jié)果如下:

如結(jié)果所示, 10個(gè)運(yùn)動(dòng)員在8秒內(nèi)分別準(zhǔn)備好,等待教練發(fā)令后,同時(shí)開(kāi)跑。
此場(chǎng)景中,10個(gè)運(yùn)動(dòng)員相當(dāng)于10個(gè)協(xié)程, 同時(shí)等待主協(xié)程的命令,使用Broadcast() 喚醒所有阻塞的子協(xié)程。
注意事項(xiàng)
使用 Cond,最容易踩的坑就是調(diào)用 Wait() 方法之前,調(diào)用者沒(méi)有持有鎖或沒(méi)有檢查輔助條件。
在如上示例代碼中,假如把調(diào)用 Wait() 方法前后的加鎖和釋放鎖的代碼注釋掉,運(yùn)行代碼會(huì)導(dǎo)致程序 panic。原因是調(diào)用 Wait 方法,會(huì)先把調(diào)用者放入等待隊(duì)列中,然后釋放鎖。此時(shí)如果在未持有鎖時(shí)調(diào)用釋放鎖的方法,就會(huì)導(dǎo)致程序 panic。
到此這篇關(guān)于Go中sync 包的 Cond 使用的文章就介紹到這了,更多相關(guān)go sync包c(diǎn)ond使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言如何實(shí)現(xiàn)將[][]byte轉(zhuǎn)為io.Reader
本文主要介紹了如何在Go語(yǔ)言中實(shí)現(xiàn)將[][]byte轉(zhuǎn)換為io.Reader,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2025-02-02
淺談Go連接池的設(shè)計(jì)與實(shí)現(xiàn)
本文主要介紹了淺談Go連接池的設(shè)計(jì)與實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
一文帶你了解Go語(yǔ)言中的I/O接口設(shè)計(jì)
I/O?操作在編程中扮演著至關(guān)重要的角色,它涉及程序與外部世界之間的數(shù)據(jù)交換,下面我們就來(lái)簡(jiǎn)單了解一下Go語(yǔ)言中的?I/O?接口設(shè)計(jì)吧2023-06-06
解決Golang在Web開(kāi)發(fā)時(shí)前端莫名出現(xiàn)的空白換行
最近在使用Go語(yǔ)言開(kāi)發(fā)Web時(shí),在前端莫名出現(xiàn)了空白換行,找了網(wǎng)上的一些資料終于找到了解決方法,現(xiàn)在分享給大家,有需要的可以參考。2016-08-08
Go語(yǔ)言error的設(shè)計(jì)理念及背景演化詳解
這篇文章主要為大家介紹了Go語(yǔ)言error的設(shè)計(jì)理念及背景演化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
一文帶你吃透Golang中net/http標(biāo)準(zhǔn)庫(kù)服務(wù)端
這篇文章將從服務(wù)端(Server)作為切入點(diǎn)和大家分享一下Go語(yǔ)言net/http標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)邏輯,進(jìn)而一步步分析http標(biāo)準(zhǔn)庫(kù)內(nèi)部是如何運(yùn)作的,感興趣的可以了解下2024-03-03
Golang庫(kù)插件注冊(cè)加載機(jī)制的問(wèn)題
這篇文章主要介紹了Golang庫(kù)插件注冊(cè)加載機(jī)制,這里說(shuō)的插件并不是指的golang原生的可以在buildmode中加載指定so文件的那種加載機(jī)制,需要的朋友可以參考下2022-03-03
Golang發(fā)送http GET請(qǐng)求的示例代碼
這篇文章主要介紹了Golang發(fā)送http GET請(qǐng)求的示例代碼,幫助大家更好的理解和使用golang,感興趣的朋友可以了解下2020-12-12

