網站首頁 編程語言 正文
一、臨界資源
臨界資源: 指并發環境中多個進程/線程/協程共享的資源。
但是在并發編程中對臨界資源的處理不當, 往往會導致數據不一致的問題。
示例代碼:
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) }
我們通過終端命令來執行:
能夠發現一處被多個goroutine共享的數據。
二、臨界資源安全問題
并發本身并不復雜,但是因為有了資源競爭的問題,就使得我們開發出好的并發程序變得復雜起來,因為會引起很多莫名其妙的問題。
如果多個goroutine在訪問同一個數據資源的時候,其中一個線程修改了數據,那么這個數值就被修改了,對于其他的goroutine來講,這個數值可能是不對的。
舉個例子,我們通過并發來實現火車站售票這個程序。一共有100張票,4個售票口同時出售。
我們先來看一下示例代碼:
package main ? import ( "fmt" "math/rand" "time" ) ? //全局變量 var ticket = 10 // 100張票 ? func main() { /* 4個goroutine,模擬4個售票口,4個子程序操作同一個共享數據。 */ 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,"售罄,沒有票了。。") break } } } ?
我們為了更好的觀察臨界資源問題,每個goroutine先睡眠一個隨機數,然后再售票,我們發現程序的運行結果,還可以賣出編號為負數的票。
分析:
我們的賣票邏輯是先判斷票數的編號是否為負數,如果大于0,然后我們就進行賣票,只不過在賣票錢先睡眠,然后再賣,假如說此時已經賣票到只剩最后1張了,某一個goroutine持有了CPU的時間片,那么它再片段是否有票的時候,條件是成立的,所以它可以賣票編號為1的最后一張票。但是因為它在賣之前,先睡眠了,那么其他的goroutine就會持有CPU的時間片,而此時這張票還沒有被賣出,那么第二個goroutine再判斷是否有票的時候,條件也是成立的,那么它可以賣出這張票,然而它也進入了睡眠。。其他的第三個第四個goroutine都是這樣的邏輯,當某個goroutine醒來的時候,不會再判斷是否有票,而是直接售出,這樣就賣出最后一張票了,然而其他的goroutine醒來的時候,就會陸續賣出了第0張,-1張,-2張。
這就是臨界資源的不安全問題。某一個goroutine在訪問某個數據資源的時候,按照數值,已經判斷好了條件,然后又被其他的goroutine搶占了資源,并修改了數值,等這個goroutine再繼續訪問這個數據的時候,數值已經不對了。
三、臨界資源安全問題的解決
要想解決臨界資源安全的問題,很多編程語言的解決方案都是同步。通過上鎖的方式,某一時間段,只能允許一個goroutine來訪問這個共享數據,當前goroutine訪問完畢,解鎖后,其他的goroutine才能來訪問。
我們可以借助于sync包下的鎖操作。
示例代碼:
package main ? import ( "fmt" "math/rand" "time" "sync" ) ? //全局變量 var ticket = 10 // 100張票 ? var wg sync.WaitGroup var matex sync.Mutex // 創建鎖頭 ? func main() { /* 4個goroutine,模擬4個售票口,4個子程序操作同一個共享數據。 */ 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, "售罄,沒有票了。。") break } matex.Unlock() //解鎖 } } ?
運行結果:
四、寫在最后
在Go的并發編程中有一句很經典的話:不要以共享內存的方式去通信,而要以通信的方式去共享內存。
在Go語言中并不鼓勵用鎖保護共享狀態的方式在不同的Goroutine中分享信息(以共享內存的方式去通信)。而是鼓勵通過channel將共享狀態或共享狀態的變化在各個Goroutine之間傳遞(以通信的方式去共享內存),這樣同樣能像用鎖一樣保證在同一的時間只有一個Goroutine訪問共享狀態。
當然,在主流的編程語言中為了保證多線程之間共享數據安全性和一致性,都會提供一套基本的同步工具集,如鎖,條件變量,原子操作等等。Go語言標準庫也毫不意外的提供了這些同步機制,使用方式也和其他語言也差不多。
原文鏈接:https://blog.csdn.net/qfzhangxu/article/details/107356601
相關推薦
- 2022-06-25 JQuery獲取對象的方式介紹_jquery
- 2022-05-14 面試分析分布式架構Redis熱點key大Value解決方案_Redis
- 2022-04-18 python?如何使用requests下載文件_python
- 2022-07-18 linux引導和計劃任務
- 2023-06-16 瞅一眼就能學會的GO并發編程使用教程_Golang
- 2023-03-26 rollup?cli開發全面系統性rollup源碼分析_其它
- 2023-01-03 python案例中Flask全局配置示例詳解_python
- 2022-11-04 C++多態特性之派生與虛函數與模板詳細介紹_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支