go語(yǔ)言同步教程之條件變量
Go的標(biāo)準(zhǔn)庫(kù)中有一個(gè)類(lèi)型叫條件變量:sync.Cond。這種類(lèi)型與互斥鎖和讀寫(xiě)鎖不同,它不是開(kāi)箱即用的,它需要與互斥鎖組合使用:
// NewCond returns a new Cond with Locker l. func NewCond(l Locker) *Cond { return &Cond{L: l} } // A Locker represents an object that can be locked and unlocked. type Locker interface { Lock() Unlock() }
通過(guò)使用 NewCond 函數(shù)可以返回 *sync.Cond 類(lèi)型的結(jié)果, *sync.Cond 我們主要操作其三個(gè)方法,分別是:
wait():等待通知
Signal():?jiǎn)尾ネㄖ?/p>
Broadcast():廣播通知
具體的函數(shù)說(shuō)明如下:
// Wait atomically unlocks c.L and suspends execution // of the calling goroutine. After later resuming execution, // Wait locks c.L before returning. Unlike in other systems, // Wait cannot return unless awoken by Broadcast or Signal. // // Because c.L is not locked when Wait first resumes, the caller // typically cannot assume that the condition is true when // Wait returns. Instead, the caller should Wait in a loop: // // c.L.Lock() // for !condition() { // c.Wait() // } // ... make use of condition ... // c.L.Unlock() // func (c *Cond) Wait() { c.checker.check() t := runtime_notifyListAdd(&c.notify) c.L.Unlock() runtime_notifyListWait(&c.notify, t) c.L.Lock() } // Signal wakes one goroutine waiting on c, if there is any. // // It is allowed but not required for the caller to hold c.L // during the call. func (c *Cond) Signal() { c.checker.check() runtime_notifyListNotifyOne(&c.notify) } // Broadcast wakes all goroutines waiting on c. // // It is allowed but not required for the caller to hold c.L // during the call. func (c *Cond) Broadcast() { c.checker.check() runtime_notifyListNotifyAll(&c.notify) }
條件變量sync.Cond本質(zhì)上是一些正在等待某個(gè)條件的線(xiàn)程的同步機(jī)制。
sync.Cond 主要實(shí)現(xiàn)一個(gè)條件變量,假如 goroutine A 執(zhí)行前需要等待另外的goroutine B 的通知,那邊處于等待的goroutine A 會(huì)保存在一個(gè)通知列表,也就是說(shuō)需要某種變量狀態(tài)的goroutine A 將會(huì)等待/Wait在那里,當(dāng)某個(gè)時(shí)刻狀態(tài)改變時(shí)負(fù)責(zé)通知的goroutine B 通過(guò)對(duì)條件變量通知的方式(Broadcast,Signal)來(lái)通知處于等待條件變量的goroutine A, 這樣便可首先一種“消息通知”的同步機(jī)制。
以go的http處理為例,在Go的源碼中http模塊server部分源碼中所示,當(dāng)需要處理一個(gè)新的連接的時(shí)候,若連接conn是實(shí)現(xiàn)自*tls.Conn的情況下,會(huì)進(jìn)行相關(guān)的客戶(hù)端與服務(wù)端的“握手”處理Handshake(), 入口代碼如下:
if tlsConn, ok := c.rwc.(*tls.Conn); ok { if d := c.server.ReadTimeout; d != 0 { c.rwc.SetReadDeadline(time.Now().Add(d)) } if d := c.server.WriteTimeout; d != 0 { c.rwc.SetWriteDeadline(time.Now().Add(d)) } if err := tlsConn.Handshake(); err != nil { c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err) return } c.tlsState = new(tls.ConnectionState) *c.tlsState = tlsConn.ConnectionState() if proto := c.tlsState.NegotiatedProtocol; validNPN(proto) { if fn := c.server.TLSNextProto[proto]; fn != nil { h := initNPNRequest{tlsConn, serverHandler{c.server}} fn(c.server, tlsConn, h) } return } }
其中的Handshake函數(shù)代碼通過(guò)使用條件變量的方式來(lái)處理新連接握手調(diào)用的同步問(wèn)題:
func (c *Conn) Handshake() error { c.handshakeMutex.Lock() defer c.handshakeMutex.Unlock() for { if err := c.handshakeErr; err != nil { return err } if c.handshakeComplete { return nil } if c.handshakeCond == nil { break } c.handshakeCond.Wait() } c.handshakeCond = sync.NewCond(&c.handshakeMutex) c.handshakeMutex.Unlock() c.in.Lock() defer c.in.Unlock() c.handshakeMutex.Lock() if c.handshakeErr != nil || c.handshakeComplete { panic("handshake should not have been able to complete after handshakeCond was set") } if c.isClient { c.handshakeErr = c.clientHandshake() } else { c.handshakeErr = c.serverHandshake() } if c.handshakeErr == nil { c.handshakes++ } else { c.flush() } if c.handshakeErr == nil && !c.handshakeComplete { panic("handshake should have had a result.") } c.handshakeCond.Broadcast() c.handshakeCond = nil return c.hand
我們也可以再通過(guò)一個(gè)例子熟悉sync.Cond的使用:
我們嘗試實(shí)現(xiàn)一個(gè)讀寫(xiě)同步的例子,需求是:我們有數(shù)個(gè)讀取器和數(shù)個(gè)寫(xiě)入器,讀取器必須依賴(lài)寫(xiě)入器對(duì)緩存區(qū)進(jìn)行數(shù)據(jù)寫(xiě)入后,才可從緩存區(qū)中對(duì)數(shù)據(jù)進(jìn)行讀出。我們思考下,要實(shí)現(xiàn)類(lèi)似的功能,除了使用channel,還能如何做?
寫(xiě)入器每次完成寫(xiě)入數(shù)據(jù)后,它都需要某種通知機(jī)制廣播給處于阻塞狀態(tài)的讀取器,告訴它們可以對(duì)數(shù)據(jù)進(jìn)行訪(fǎng)問(wèn),這其實(shí)跟sync.Cond 的 廣播機(jī)制是不是很像? 有了這個(gè)廣播機(jī)制,我們可以通過(guò)sync.Cond來(lái)實(shí)現(xiàn)這個(gè)例子了:
package main import ( "bytes" "fmt" "io" "sync" "time" ) type MyDataBucket struct { br *bytes.Buffer gmutex *sync.RWMutex rcond *sync.Cond //讀操作需要用到的條件變量 } func NewDataBucket() *MyDataBucket { buf := make([]byte, 0) db := &MyDataBucket{ br: bytes.NewBuffer(buf), gmutex: new(sync.RWMutex), } db.rcond = sync.NewCond(db.gmutex.RLocker()) return db } func (db *MyDataBucket) Read(i int) { db.gmutex.RLock() defer db.gmutex.RUnlock() var data []byte var d byte var err error for { //讀取一個(gè)字節(jié) if d, err = db.br.ReadByte(); err != nil { if err == io.EOF { if string(data) != "" { fmt.Printf("reader-%d: %s\n", i, data) } db.rcond.Wait() data = data[:0] continue } } data = append(data, d) } } func (db *MyDataBucket) Put(d []byte) (int, error) { db.gmutex.Lock() defer db.gmutex.Unlock() //寫(xiě)入一個(gè)數(shù)據(jù)塊 n, err := db.br.Write(d) db.rcond.Broadcast() return n, err } func main() { db := NewDataBucket() go db.Read(1) go db.Read(2) for i := 0; i < 10; i++ { go func(i int) { d := fmt.Sprintf("data-%d", i) db.Put([]byte(d)) }(i) time.Sleep(100 * time.Millisecond) } }
當(dāng)使用sync.Cond的時(shí)候有兩點(diǎn)移動(dòng)要注意的:
- 一定要在調(diào)用cond.Wait方法前,鎖定與之關(guān)聯(lián)的讀寫(xiě)鎖
- 一定不要忘記在cond.Wait后,若數(shù)據(jù)已經(jīng)處理完畢,在返回前要對(duì)與之關(guān)聯(lián)的讀寫(xiě)鎖進(jìn)行解鎖。
如下面 Wait() 的源碼所示,Cond.Wait會(huì)自動(dòng)釋放鎖等待信號(hào)的到來(lái),當(dāng)信號(hào)到來(lái)后,第一個(gè)獲取到信號(hào)的Wait將繼續(xù)往下執(zhí)行并從新上鎖
func (c *Cond) Wait() { c.checker.check() t := runtime_notifyListAdd(&c.notify) c.L.Unlock() runtime_notifyListWait(&c.notify, t) c.L.Lock() }
如果不釋放鎖, 其它收到信號(hào)的gouroutine將阻塞無(wú)法繼續(xù)執(zhí)行。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
golang求連續(xù)子數(shù)組的最大和實(shí)例
這篇文章主要介紹了golang求連續(xù)子數(shù)組的最大和實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12詳解Golang中interface{}的注意事項(xiàng)
學(xué)習(xí)?golang?,對(duì)于?interface{}?接口類(lèi)型,我們一定繞不過(guò),這篇文章咱們就來(lái)一起來(lái)看看?使用?interface{}?的時(shí)候,都有哪些注意事項(xiàng)吧2023-03-03Goland遠(yuǎn)程連接Linux進(jìn)行項(xiàng)目開(kāi)發(fā)的實(shí)現(xiàn)
有的時(shí)候我們的開(kāi)發(fā)代碼要在linux服務(wù)器上運(yùn)行,本文主要介紹了Goland遠(yuǎn)程連接Linux進(jìn)行項(xiàng)目開(kāi)發(fā)的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06wind10 idea中 go 開(kāi)發(fā)環(huán)境搭建教程圖解
這篇文章主要介紹了wind10 idea中 go 開(kāi)發(fā)環(huán)境搭建過(guò)程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06利用ChatGPT編寫(xiě)一個(gè)Golang圖像壓縮函數(shù)
這篇文章主要為大家詳細(xì)介紹了如何利用ChatGPT幫我們寫(xiě)了一個(gè)Golang圖像壓縮函數(shù),文中的示例代碼簡(jiǎn)潔易懂,感興趣的小伙伴可以嘗試一下2023-04-04詳解如何在Golang中實(shí)現(xiàn)CORS(跨域)
很多時(shí)候,需要允許Web應(yīng)用程序在不同域之間(跨域)實(shí)現(xiàn)共享資源,本文將簡(jiǎn)介跨域、CORS的概念,以及如何在Golang中如何實(shí)現(xiàn)CORS,文中有詳細(xì)的示例代碼,需要的朋友可以參考下2023-10-10使用Go和Gorm實(shí)現(xiàn)讀取SQLCipher加密數(shù)據(jù)庫(kù)
本文檔主要描述通過(guò)Go和Gorm實(shí)現(xiàn)生成和讀取SQLCipher加密數(shù)據(jù)庫(kù)以及其中踩的一些坑,文章通過(guò)代碼示例講解的非常詳細(xì), 對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-06-06在Go語(yǔ)言單元測(cè)試中解決HTTP網(wǎng)絡(luò)依賴(lài)問(wèn)題
在 Go 語(yǔ)言中,我們需要找到一種可靠的方法來(lái)測(cè)試 HTTP 請(qǐng)求和響應(yīng),本文將探討在 Go 中進(jìn)行 HTTP 應(yīng)用測(cè)試時(shí),如何解決應(yīng)用程序的依賴(lài)問(wèn)題,以確保我們能夠編寫(xiě)出可靠的測(cè)試用例,需要的朋友可以參考下2023-07-07