golang中的条件变量
簡介
var mailbox uint8 var lock sync.RWMutex sendCond := sync.NewCond(&lock) recvCond := sync.NewCond(lock.RLocker())本身不是鎖,要與鎖結合使用
go標準庫中的sync.Cond類型代表了條件變量.
條件變量要與鎖(互斥鎖,或者讀寫鎖)一起使用.成員變量L代表與條件變量搭配使用的鎖
對應有3個常用方法: Wait, Signal, Broadcast
func (c *Cond) Wait()- 阻塞等待條件變量滿足,等醒
- 釋放已掌握的互斥鎖相當于cond.L.Unlock().注意:1,2兩步為一個原子操作
- 當被喚醒的時候,Wait()返回,解除阻塞并重新獲取互斥鎖.相當于cond.L.Lock()
為什么wait要做那3步操作,因為你在等待的時候,把鎖釋放掉啊,讓別人訪問公共空間,然后你被喚醒的時候,你需要拿到鎖,拿到鎖才能對公共空間訪問
func (c *Cond) Signal()Signal()通知的順序是根據原來加入通知列表(Wait())的先入先出
若沒有Wait(),也不會報錯
單發通知,一次一個,給一個正在等待(阻塞)在該條件變量上的協程發送通知
func (c *Cond) Broadcast()廣播通知,都醒了,驚群,給正在等待(阻塞)在該條件變量上的所有協程發送通知
生產者消費者
代碼注意點是,那里用for,不用for用if的haul,喚醒后往下執行,如果容量滿的話是會阻塞的,如果是for的話,wait好的話會再次判斷下的,if沒有再次判斷
用if的話,會出現問題而且是偶爾的出現,因為if里面如果喚醒,那么往下如果阻塞,阻塞的話,消費者無法喚醒他了,因為wait已經走過了
//創建全局條件變量 var cond sync.Cond//生產者 func producer(out chan<- int, idx int) {for {//條件變量對應互斥鎖加鎖cond.L.Lock()//注意這邊用for不能用if//循環判斷,如果條件不滿足直接跳過,滿足就等待,因為怕喚醒后有多個生產者一下子讓他充滿//讓他解開的同時,順便判斷下,怕其他生產者已經寫到了3個for len(out) == 3 { //產品區滿,等待消費者cond.Wait() //掛起當前協程,等待條件變量滿足,被消費者喚醒}num := rand.Intn(1000) //產生一個隨機數out <- numfmt.Println("---生產者---產生數據---剩余多少個---", idx, num, len(out))cond.L.Unlock() //生產結束,解鎖互斥鎖cond.Signal() //喚醒阻塞的消費者time.Sleep(time.Second)} }//消費者 func consumer(in <-chan int, idx int) {for {//條件變量對應互斥鎖加鎖(與生產者是同一個)cond.L.Lock()//產品區為空,等待生產者生產for len(in) == 0 {cond.Wait()}//將channel中的數據讀取(消費)num := <-infmt.Println("---消費者---消費數據---公共區剩余多少個---", idx, num, len(in))//消費結束,解鎖互斥鎖cond.L.Unlock()//喚醒阻塞的生產者cond.Signal()//消費者休息一會兒,給其他協程機會time.Sleep(time.Millisecond * 500)} }func main() {rand.Seed(time.Now().UnixNano())//產品區(公共區)使用channel模擬product := make(chan int, 3)//創建互斥鎖和條件變量cond.L = new(sync.Mutex)//生產者for i := 0; i < 5; i++ {go producer(product, i+1)}//消費者for i := 0; i < 3; i++ {go consumer(product, i+1)}for {;} }注意點
我們在利用條件變量等待通知的時候,需要在它基于的那個互斥鎖保護下進行。而在進行單發通知或廣播通知的時候,卻是恰恰相反的,也就是說,需要在對應的互斥鎖解鎖之后再做這兩種操作。
條件變量并不是被用來保護臨界區和共享資源的,它是用于協調想要訪問共享資源的那些線程的。當共享資源的狀態發生變化時,它可以被用來通知被互斥鎖阻塞的線程。
把調用它的 goroutine(也就是當前的 goroutine)加入到當前條件變量的通知隊列中。
解鎖當前的條件變量基于的那個互斥鎖。
讓當前的 goroutine 處于等待狀態,等到通知到來時再決定是否喚醒它。此時,這個 goroutine 就會阻塞在調用這個Wait方法的那行代碼上。
如果通知到來并且決定喚醒這個 goroutine,那么就在喚醒它之后重新鎖定當前條件變量基于的互斥鎖。自此之后,當前的 goroutine 就會繼續執行后面的代碼了
如果一個 goroutine 因收到通知而被喚醒,但卻發現共享資源的狀態,依然不符合它的要求,那么就應該再次調用條件變量的Wait方法,并繼續等待下次通知的到來。
條件變量的Wait方法總會把當前的 goroutine 添加到通知隊列的隊尾,而它的Signal方法總會從通知隊列的隊首開始,查找可被喚醒的 goroutine。所以,因Signal方法的通知,而被喚醒的 goroutine 一般都是最早等待的那一個。
最后,請注意,條件變量的通知具有即時性。也就是說,如果發送通知的時候沒有 goroutine 為此等待,那么該通知就會被直接丟棄。在這之后才開始等待的 goroutine 只可能被后面的通知喚醒。
適合什么
條件變量適合保護那些可執行兩個對立操作的共享資源。比如,一個既可讀又可寫的共享文件。又比如,既有生產者又有消費者的產品池。
盡量少的鎖爭
相對應的,我們在調用條件變量的 Wait 方法的時候,應該處在其中的鎖的保護之下。因為有同一個鎖保護,所以不可能有多個 goroutine 同時執行到這個 Wait 方法調用,也就不可能存在針對其中鎖的重復解鎖。
對于同一個鎖,多個 goroutine 對它重復鎖定時只會有一個成功,其余的會阻塞;多個 goroutine 對它重復解鎖時也只會有一個成功,但其余的會拋 panic
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的golang中的条件变量的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: golang中的读写锁
- 下一篇: golang中的嵌套