GO語(yǔ)言臨界資源安全問(wèn)題的深入理解
一、臨界資源
臨界資源: 指并發(fā)環(huán)境中多個(gè)進(jìn)程/線程/協(xié)程共享的資源。
但是在并發(fā)編程中對(duì)臨界資源的處理不當(dāng), 往往會(huì)導(dǎo)致數(shù)據(jù)不一致的問(wèn)題。
示例代碼:
package main ? import ( "fmt" "time" ) ? func main() { a := 1 go func() { a = 2 fmt.Println("子goroutine。。",a) }() a = 3 time.Sleep(1) fmt.Println("main goroutine。。",a) }
我們通過(guò)終端命令來(lái)執(zhí)行:
能夠發(fā)現(xiàn)一處被多個(gè)goroutine共享的數(shù)據(jù)。
二、臨界資源安全問(wèn)題
并發(fā)本身并不復(fù)雜,但是因?yàn)橛辛速Y源競(jìng)爭(zhēng)的問(wèn)題,就使得我們開發(fā)出好的并發(fā)程序變得復(fù)雜起來(lái),因?yàn)闀?huì)引起很多莫名其妙的問(wèn)題。
如果多個(gè)goroutine在訪問(wèn)同一個(gè)數(shù)據(jù)資源的時(shí)候,其中一個(gè)線程修改了數(shù)據(jù),那么這個(gè)數(shù)值就被修改了,對(duì)于其他的goroutine來(lái)講,這個(gè)數(shù)值可能是不對(duì)的。
舉個(gè)例子,我們通過(guò)并發(fā)來(lái)實(shí)現(xiàn)火車站售票這個(gè)程序。一共有100張票,4個(gè)售票口同時(shí)出售。
我們先來(lái)看一下示例代碼:
package main ? import ( "fmt" "math/rand" "time" ) ? //全局變量 var ticket = 10 // 100張票 ? func main() { /* 4個(gè)goroutine,模擬4個(gè)售票口,4個(gè)子程序操作同一個(gè)共享數(shù)據(jù)。 */ go saleTickets("售票口1") // g1,100 go saleTickets("售票口2") // g2,100 go saleTickets("售票口3") //g3,100 go saleTickets("售票口4") //g4,100 ? time.Sleep(5*time.Second) } ? func saleTickets(name string) { rand.Seed(time.Now().UnixNano()) //for i:=1;i<=100;i++{ // fmt.Println(name,"售出:",i) //} for { //ticket=1 if ticket > 0 { //g1,g3,g2,g4 //睡眠 time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) // g1 ,g3, g2,g4 fmt.Println(name, "售出:", ticket) // 1 , 0, -1 , -2 ticket-- //0 , -1 ,-2 , -3 } else { fmt.Println(name,"售罄,沒(méi)有票了。。") break } } } ?
我們?yōu)榱烁玫挠^察臨界資源問(wèn)題,每個(gè)goroutine先睡眠一個(gè)隨機(jī)數(shù),然后再售票,我們發(fā)現(xiàn)程序的運(yùn)行結(jié)果,還可以賣出編號(hào)為負(fù)數(shù)的票。
分析:
我們的賣票邏輯是先判斷票數(shù)的編號(hào)是否為負(fù)數(shù),如果大于0,然后我們就進(jìn)行賣票,只不過(guò)在賣票錢先睡眠,然后再賣,假如說(shuō)此時(shí)已經(jīng)賣票到只剩最后1張了,某一個(gè)goroutine持有了CPU的時(shí)間片,那么它再片段是否有票的時(shí)候,條件是成立的,所以它可以賣票編號(hào)為1的最后一張票。但是因?yàn)樗谫u之前,先睡眠了,那么其他的goroutine就會(huì)持有CPU的時(shí)間片,而此時(shí)這張票還沒(méi)有被賣出,那么第二個(gè)goroutine再判斷是否有票的時(shí)候,條件也是成立的,那么它可以賣出這張票,然而它也進(jìn)入了睡眠。。其他的第三個(gè)第四個(gè)goroutine都是這樣的邏輯,當(dāng)某個(gè)goroutine醒來(lái)的時(shí)候,不會(huì)再判斷是否有票,而是直接售出,這樣就賣出最后一張票了,然而其他的goroutine醒來(lái)的時(shí)候,就會(huì)陸續(xù)賣出了第0張,-1張,-2張。
這就是臨界資源的不安全問(wèn)題。某一個(gè)goroutine在訪問(wèn)某個(gè)數(shù)據(jù)資源的時(shí)候,按照數(shù)值,已經(jīng)判斷好了條件,然后又被其他的goroutine搶占了資源,并修改了數(shù)值,等這個(gè)goroutine再繼續(xù)訪問(wèn)這個(gè)數(shù)據(jù)的時(shí)候,數(shù)值已經(jīng)不對(duì)了。
三、臨界資源安全問(wèn)題的解決
要想解決臨界資源安全的問(wèn)題,很多編程語(yǔ)言的解決方案都是同步。通過(guò)上鎖的方式,某一時(shí)間段,只能允許一個(gè)goroutine來(lái)訪問(wèn)這個(gè)共享數(shù)據(jù),當(dāng)前goroutine訪問(wèn)完畢,解鎖后,其他的goroutine才能來(lái)訪問(wèn)。
我們可以借助于sync包下的鎖操作。
示例代碼:
package main ? import ( "fmt" "math/rand" "time" "sync" ) ? //全局變量 var ticket = 10 // 100張票 ? var wg sync.WaitGroup var matex sync.Mutex // 創(chuàng)建鎖頭 ? func main() { /* 4個(gè)goroutine,模擬4個(gè)售票口,4個(gè)子程序操作同一個(gè)共享數(shù)據(jù)。 */ wg.Add(4) go saleTickets("售票口1") // g1,100 go saleTickets("售票口2") // g2,100 go saleTickets("售票口3") //g3,100 go saleTickets("售票口4") //g4,100 wg.Wait() // main要等待。。。 ? //time.Sleep(5*time.Second) } ? func saleTickets(name string) { rand.Seed(time.Now().UnixNano()) defer wg.Done() //for i:=1;i<=100;i++{ // fmt.Println(name,"售出:",i) //} for { //ticket=1 matex.Lock() if ticket > 0 { //g1,g3,g2,g4 //睡眠 time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) // g1 ,g3, g2,g4 fmt.Println(name, "售出:", ticket) // 1 , 0, -1 , -2 ticket-- //0 , -1 ,-2 , -3 } else { matex.Unlock() //解鎖 fmt.Println(name, "售罄,沒(méi)有票了。。") break } matex.Unlock() //解鎖 } } ?
運(yùn)行結(jié)果:
四、寫在最后
在Go的并發(fā)編程中有一句很經(jīng)典的話:不要以共享內(nèi)存的方式去通信,而要以通信的方式去共享內(nèi)存。
在Go語(yǔ)言中并不鼓勵(lì)用鎖保護(hù)共享狀態(tài)的方式在不同的Goroutine中分享信息(以共享內(nèi)存的方式去通信)。而是鼓勵(lì)通過(guò)channel將共享狀態(tài)或共享狀態(tài)的變化在各個(gè)Goroutine之間傳遞(以通信的方式去共享內(nèi)存),這樣同樣能像用鎖一樣保證在同一的時(shí)間只有一個(gè)Goroutine訪問(wèn)共享狀態(tài)。
當(dāng)然,在主流的編程語(yǔ)言中為了保證多線程之間共享數(shù)據(jù)安全性和一致性,都會(huì)提供一套基本的同步工具集,如鎖,條件變量,原子操作等等。Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)也毫不意外的提供了這些同步機(jī)制,使用方式也和其他語(yǔ)言也差不多。
到此這篇關(guān)于GO語(yǔ)言臨界資源安全問(wèn)題的深入理解的文章就介紹到這了,更多相關(guān)GO語(yǔ)言臨界資源安全 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
細(xì)細(xì)探究Go 泛型generic設(shè)計(jì)
這篇文章主要帶大家細(xì)細(xì)探究了Go 泛型generic設(shè)計(jì)及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Golang實(shí)現(xiàn)對(duì)map的并發(fā)讀寫的方法示例
這篇文章主要介紹了Golang實(shí)現(xiàn)對(duì)map的并發(fā)讀寫的方法示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03Go并發(fā)控制Channel使用場(chǎng)景分析
使用channel來(lái)控制子協(xié)程的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,缺點(diǎn)是當(dāng)需要大量創(chuàng)建協(xié)程時(shí)就需要有相同數(shù)量的channel,而且對(duì)于子協(xié)程繼續(xù)派生出來(lái)的協(xié)程不方便控制2021-07-07Golang map實(shí)踐及實(shí)現(xiàn)原理解析
這篇文章主要介紹了Golang map實(shí)踐以及實(shí)現(xiàn)原理,Go 語(yǔ)言中,通過(guò)哈希查找表實(shí)現(xiàn) map,用鏈表法解決哈希沖突,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06golang使用net/rpc庫(kù)實(shí)現(xiàn)rpc
這篇文章主要為大家詳細(xì)介紹了golang如何使用net/rpc庫(kù)實(shí)現(xiàn)rpc,文章的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的小伙伴可以參考一下2024-01-01深入string理解Golang是怎樣實(shí)現(xiàn)的
這篇文章主要為大家介紹了深入string理解Golang是怎樣實(shí)現(xiàn)的原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04