Go語言擴(kuò)展原語之Semaphore的用法詳解
概述
信號(hào)量是并發(fā)編程中常見的一種同步機(jī)制,在需要控制訪問資源的進(jìn)程數(shù)量時(shí)就會(huì)用到信號(hào)量,它會(huì)保證持有的計(jì)數(shù)器在 0 到初始化的權(quán)重之間波動(dòng)。
- 每次獲取的資源都會(huì)將信號(hào)量中的計(jì)數(shù)器減去對(duì)應(yīng)的數(shù)值,在釋放時(shí)重新加回來
- 當(dāng)遇到計(jì)數(shù)器大于信號(hào)量大小時(shí),會(huì)進(jìn)入休眠等待其他線程釋放信號(hào)
Go語言的擴(kuò)展包中提供了帶權(quán)重的信號(hào)量 semaphore.Weighted,我們可以按照不同的權(quán)重管理資源的訪問,這種結(jié)構(gòu)體暴露了 4 個(gè)方法:
- semaphore.NewWeighted —— 用于創(chuàng)建新的信號(hào)量
- semaphore.Weighted.Acquire —— 阻塞地獲取指定權(quán)重的資源,如果當(dāng)前沒有空閑資源,會(huì)陷入休眠等待
- semaphore.Weighted.TryAcquire —— 非阻塞地獲取指定權(quán)重的資源,如果當(dāng)前沒有空閑資源,會(huì)直接返回 false
- semaphore.Weighted.Relesae —— 用于釋放指定權(quán)重的資源
結(jié)構(gòu)體
semaphore.NewWeighted 方法能提供傳入的大量權(quán)重創(chuàng)建一個(gè)指向 semaphore.Weighted 結(jié)構(gòu)體的指針:
func NewWeighted(n int64) *Weighted { w := &Weighted{size: n} return w } type Weighted struct { size int64 cur int64 mu sync.Mutex waiters list.list }
semaphore.Weighted 結(jié)構(gòu)體中包含一個(gè) waiters 列表,其中存儲(chǔ)著等待獲取資源的 Goroutine。除此之外,它還包含當(dāng)前信號(hào)量的上限以及一個(gè)計(jì)數(shù)器 cur, 這個(gè)計(jì)數(shù)器的范圍就是 [0,size]
信號(hào)量中的計(jì)數(shù)器會(huì)隨著用戶對(duì)資源的訪問和釋放而改變,引入的權(quán)重概念能夠提供更細(xì)粒度的資源訪問控制,盡可能滿足常見用例。
獲取
semaphore.Weighted.Acquire 方法能用于獲取指定權(quán)重的資源,其中包含 3 中情況:
- 當(dāng)信號(hào)量中剩余資源大于獲取的資源并且沒有等待的 Goroutine 時(shí),會(huì)直接獲取信號(hào)量
- 當(dāng)需要獲取的信號(hào)量大于 semaphore.Weighted 的上限時(shí),由于不可能滿足條件,因此會(huì)直接返回錯(cuò)誤
- 遇到其他情況時(shí),會(huì)將當(dāng)前 Goroutine 加入等待列表,并通過 select 等待調(diào)度器喚醒當(dāng)前 Goroutine,Goroutine 被喚醒后會(huì)獲取信號(hào)量
func (s *Weighted) Acquire(ctx context.Context, n int64) error { if s.size - s.cur >= n && len(s.waiters) == 0 { s.cur += n return nil } ... ready := make(chan struct{}) w := waiter{n: n, ready: ready} elem := s.waiters.PushBack(w) select { case <-ctx.Done(): err := ctx.Err() select { case <-ready: err = nil default: s.waiters.Remove(elem) } return err case <-ready: return nil } }
另一個(gè)用于獲取信號(hào)量的方法 semaphore.Weighted.TryAcquire 只會(huì)非阻塞地判斷當(dāng)前信號(hào)量是否有充足的資源,如果有,會(huì)立刻返回 true, 否則會(huì)返回 false :
func (s *Weighted) TryAcquire(n int64) bool { s.mu.Lock() success := s.size-s.cur >= n && len(s.waiters) == 0 if success { s.cur += n } s.mu.Unlock() return success }
因?yàn)?semaphore.Weighted.TryAcquire 不會(huì)等待資源的釋放,所以可能更適用于一些對(duì)延時(shí)敏感、用戶需要立刻感知結(jié)果的場(chǎng)景
釋放
當(dāng)我們要釋放信號(hào)量時(shí),semaphore.Weighted.Relesae 方法會(huì)從頭到尾遍歷 waiters 列表中全部的等待者,如果釋放資源后的信號(hào)量有充足的剩余資源,就會(huì)通過 Channel 喚醒指定的 Goroutine:
func (c *Weighted) Relesae(n int64) { s.mu.Lock() s.cur -= n for { next := w.waiters.Front() if next == nil { break } w := next.Value.(waiter) if s.size-s.cur < w.n { break } s.cur += w.n s.waiters.Remove(next) close(w.ready) } s.mu.Unlock() }
當(dāng)然,也可能會(huì)出現(xiàn)剩余資源無法喚醒 Channel 的情況,這時(shí)當(dāng)前方法釋放鎖之后會(huì)直接返回。
通過 semaphore.Weighted.Relesae 的分析我們可以發(fā)現(xiàn),如果一個(gè)信號(hào)量需要占用的資源非常多,它可能會(huì)長(zhǎng)時(shí)間無法獲取鎖,這也是 semaphore.Weighted.Acquire 引入上下文參數(shù)的原因,即為信號(hào)量的獲取設(shè)置超時(shí)時(shí)間。
小結(jié)
帶權(quán)重的信號(hào)量確實(shí)有更多的應(yīng)用場(chǎng)景,,這也是Go語言對(duì)外提供的唯一信號(hào)量實(shí)現(xiàn),使用過程中需要注意以下幾個(gè)問題:
- semaphore.Weighted.Acquire 和 semaphore.Weighted.TryAcquire 都可以適用于獲取資源,前者會(huì)阻塞獲取信號(hào)量,后者會(huì)非阻塞獲取信號(hào)量
- semaphore.Weighted.Relesae 方法會(huì)按照先進(jìn)先出的順序喚醒可以被喚醒的 Goroutine
- 如果一個(gè) Goroutine 獲取了較多的資源,由于 semaphore.Weighted.Relesae 的釋放策略可能會(huì)等待較長(zhǎng)時(shí)間
到此這篇關(guān)于Go語言擴(kuò)展原語之Semaphore的用法詳解的文章就介紹到這了,更多相關(guān)Go語言Semaphore內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言調(diào)用DeepSeek?API實(shí)現(xiàn)流式輸出和對(duì)話
DeepSeek是一個(gè)強(qiáng)大的AI模型服務(wù)平臺(tái),本文將詳細(xì)介紹如何使用Go語言調(diào)用DeepSeek?API實(shí)現(xiàn)流式輸出和對(duì)話功能,感興趣的小伙伴可以了解一下2025-02-02go語言開發(fā)中如何優(yōu)雅得關(guān)閉協(xié)程方法
這篇文章主要為大家介紹了go語言開發(fā)中如何優(yōu)雅得關(guān)閉協(xié)程方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05Go語言學(xué)習(xí)函數(shù)+結(jié)構(gòu)體+方法+接口
這篇文章主要介紹了Go語言學(xué)習(xí)函數(shù)+結(jié)構(gòu)體+方法+接口,文章圍繞主題的相關(guān)資料展開詳細(xì)的文章說明,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-05-05golang 生成對(duì)應(yīng)的數(shù)據(jù)表struct定義操作
這篇文章主要介紹了golang 生成對(duì)應(yīng)的數(shù)據(jù)表struct定義操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-04-04Go Gin框架中的binding驗(yàn)證器使用小結(jié)
Gin框架中的binding驗(yàn)證器為我們提供了簡(jiǎn)便的數(shù)據(jù)綁定和驗(yàn)證功能,通過合理使用binding和validate標(biāo)簽,我們可以確保API接口的數(shù)據(jù)合法性和完整性,這篇文章主要介紹了Go Gin框架中的binding驗(yàn)證器使用指南,需要的朋友可以參考下2024-07-07