解析golang中的並發安全和鎖問題
1. 並發安全
package main import ( "fmt" "sync" ) var ( sum int wg sync.WaitGroup ) func test() { for i := 0; i < 5000000; i++ { sum += 1 } wg.Done() } func main() { // 並發和安全鎖 wg.Add(2) go test() go test() wg.Wait() fmt.Println(sum) }
上面的代碼中我們開啟瞭兩個goroutine去累加變量x的值,這兩個goroutine在訪問和修改x變量的時候就會存在數據競爭,導致最後的結果與期待的不符。
2. 互斥鎖
package main import ( "fmt" "sync" ) var ( sum int wg sync.WaitGroup mu sync.Mutex // 定義一個互斥鎖 ) func test() { for i := 0; i < 10000000; i++ { // 互斥鎖它能夠保證同時隻能有一個goroutine去訪問共享資源 mu.Lock() sum += 1 mu.Unlock() } wg.Done() } func main() { fmt.Println(mu) // 並發和安全鎖 wg.Add(2) go test() go test() wg.Wait() fmt.Println(sum) }
使用互斥鎖能夠保證同一時間有且隻有一個goroutine進入臨界區,其他的goroutine則在等待鎖;當互斥鎖釋放後,等待的goroutine才可以獲取鎖進入臨界區,多個goroutine同時等待一個鎖時,喚醒的策略是隨機的。
3. 讀寫互斥鎖
互斥鎖是完全互斥的,但是有很多實際的場景下是讀多寫少的,當我們並發的去讀取一個資源不涉及資源修改的時候是沒有必要加鎖的,這種場景下使用讀寫鎖是更好的一種選擇。讀寫鎖在Go語言中使用sync包中的RWMutex類型。
讀寫鎖分為兩種:讀鎖和寫鎖。當一個goroutine獲取讀鎖之後,其他的goroutine如果是獲取讀鎖會繼續獲得鎖,如果是獲取寫鎖就會等待;當一個goroutine獲取寫鎖之後,其他的goroutine無論是獲取讀鎖還是寫鎖都會等待。
package main import ( "fmt" "sync" "time" ) var ( x int wg sync.WaitGroup mu sync.Mutex // 定義一個互斥鎖 rw sync.RWMutex // 定義一個讀寫鎖,註意:隻有讀多寫少的時候,讀寫鎖才能發揮其優勢 ) func write() { rw.Lock() x += 1 time.Sleep(10 * time.Millisecond) // 假設寫入時間耗費10毫秒 rw.Unlock() wg.Done() } func read() { rw.RLock() time.Sleep(time.Millisecond) rw.RUnlock() wg.Done() } func main() { start := time.Now() for i := 0; i < 10; i++ { wg.Add(1) go write() } // 寫耗時:160毫秒左右 for i := 0; i < 1000; i++ { wg.Add(1) go read() } // 讀耗時:15毫秒左右 wg.Wait() end := time.Now() fmt.Println("執行時間:", end.Sub(start)) }
需要註意的是讀寫鎖非常適合讀多寫少的場景,如果讀和寫的操作差別不大,讀寫鎖的優勢就發揮不出來。
到此這篇關於golang中的並發安全和鎖的文章就介紹到這瞭,更多相關golang並發和鎖內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!