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