成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

如何使用 atomic 包減少鎖沖突

開發 前端
Go 提供了 channel 或 mutex 等內存同步機制,有助于解決不同的問題。在共享內存的情況下,mutex 可以保護內存不發生數據競爭(data race)。不過,雖然存在兩個 mutex,但 Go 也通過 atomic 包提供了原子內存基元來提高性能。在深入研究解決方案之前,我們先回過頭來看看數據競爭。

寫在前面

本文基于 Golang 1.14

Go 提供了 channel 或 mutex 等內存同步機制,有助于解決不同的問題。在共享內存的情況下,mutex 可以保護內存不發生數據競爭(data race)。不過,雖然存在兩個 mutex,但 Go 也通過 atomic 包提供了原子內存基元來提高性能。在深入研究解決方案之前,我們先回過頭來看看數據競爭。

數據競爭

當兩個或兩個以上的 goroutine 同時訪問同一塊內存區域,并且其中至少有一個在寫時,就會發生數據競爭。雖然 map 內部有一定的機制來防止數據競爭,但一個簡單的結構體并沒有任何的機制,因此容易發生數據競爭。

為了說明數據競爭,我以一個goroutine 持續更新的配置為例向大家展示一下。 

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "sync" 
  6.  
  7. type Config struct { 
  8.     a []int 
  9.  
  10. func main() { 
  11.     cfg := &Config{} 
  12.  
  13.     // 啟動一個 writer goroutine,不斷寫入數據 
  14.     go func() { 
  15.         i := 0 
  16.  
  17.         for { 
  18.             i++ 
  19.             cfg.a = []int{i, i + 1, i + 2, i + 3, i + 4, i + 5} 
  20.         } 
  21.     }() 
  22.  
  23.     // 啟動多個 reader goroutine,不斷獲取數據 
  24.     var wg sync.WaitGroup 
  25.     for n := 0; n < 4; n++ { 
  26.         wg.Add(1) 
  27.         go func() { 
  28.             for n := 0; n < 100; n++ { 
  29.                 fmt.Printf("%#v\n", cfg) 
  30.             } 
  31.             wg.Done() 
  32.         }() 
  33.     } 
  34.  
  35.     wg.Wait() 

運行這段代碼可以清楚地看到,原本期望是運行上述代碼后,每一行的數字應該是連續的,但是由于數據競爭的存在,導致結果是非確定性的。 

  1. F:\hello>go run main.go 
  2. [...] 
  3. &main.Config{a:[]int{180954, 180962, 180967, 180972, 180977, 180983}} 
  4. &main.Config{a:[]int{181296, 181304, 181311, 181318, 181322, 181323}} 
  5. &main.Config{a:[]int{181607, 181617, 181624, 181631, 181636, 181643}} 

我們可以在運行時加入參數 --race 看一下結果: 

  1. F:\hello>go run --race main.go 
  2. [...] 
  3. &main.Config{a:[]int(nil)} 
  4. ================== 
  5. &main.Config{a:[]int(nil)} 
  6. WARNING: DATA RACE&main.Config{a:[]int(nil)} 
  7.  
  8. Read at 0x00c00000c210 by Goroutine 9: 
  9.   reflect.Value.Int() 
  10.       D:/Go/src/reflect/value.go:988 +0x3584 
  11.   fmt.(*pp).printValue() 
  12.       D:/Go/src/fmt/print.go:749 +0x3590 
  13.   fmt.(*pp).printValue() 
  14.       D:/Go/src/fmt/print.go:860 +0x8f2 
  15.   fmt.(*pp).printValue() 
  16.       D:/Go/src/fmt/print.go:810 +0x289a 
  17.   fmt.(*pp).printValue() 
  18.       D:/Go/src/fmt/print.go:880 +0x261c 
  19.   fmt.(*pp).printArg() 
  20.       D:/Go/src/fmt/print.go:716 +0x26b 
  21.   fmt.(*pp).doPrintf() 
  22.       D:/Go/src/fmt/print.go:1030 +0x326 
  23.   fmt.Fprintf() 
  24.       D:/Go/src/fmt/print.go:204 +0x86 
  25.   fmt.Printf() 
  26.       D:/Go/src/fmt/print.go:213 +0xbc 
  27.   main.main.func2() 
  28.       F:/hello/main.go:31 +0x42 
  29.  
  30. Previous write at 0x00c00000c210 by goroutine 7: 
  31.   main.main.func1() 
  32.       F:/hello/main.go:21 +0x66 
  33.  
  34. goroutine 9 (running) created at
  35.   main.main() 
  36.       F:/hello/main.go:29 +0x124 
  37.  
  38. goroutine 7 (running) created at
  39.   main.main() 
  40.       F:/hello/main.go:16 +0x95 
  41. ================== 

為了避免同時讀寫過程中產生的數據競爭最常采用的方法可能是使用 mutex 或 atomic 包。

Mutex?還是 Atomic?

標準庫在 sync 包提供了兩種互斥鎖 :sync.Mutex 和 sync.RWMutex。后者在你的程序需要處理多個讀操作和極少的寫操作時進行了優化。

針對上面代碼中產生的數據競爭問題,我們看一下,如何解決呢?

使用 sync.Mutex 解決數據競爭 

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "sync" 
  6.  
  7. // Config 定義一個結構體用于模擬存放配置數據 
  8. type Config struct { 
  9.     a []int 
  10.  
  11. func main() { 
  12.     cfg := &Config{} 
  13.     var mux sync.RWMutex 
  14.  
  15.     // 啟動一個 writer goroutine,不斷寫入數據 
  16.     go func() { 
  17.         i := 0 
  18.  
  19.         for { 
  20.             i++ 
  21.             // 進行數據寫入時,先通過鎖進行鎖定 
  22.             mux.Lock() 
  23.             cfg.a = []int{i, i + 1, i + 2, i + 3, i + 4, i + 5} 
  24.             mux.Unlock() 
  25.         } 
  26.     }() 
  27.  
  28.     // 啟動多個 reader goroutine,不斷獲取數據 
  29.     var wg sync.WaitGroup 
  30.     for n := 0; n < 4; n++ { 
  31.         wg.Add(1) 
  32.         go func() { 
  33.             for n := 0; n < 100; n++ { 
  34.                 // 因為這里只是需要讀取數據,所以只需要加一個讀鎖即可 
  35.                 mux.RLock() 
  36.                 fmt.Printf("%#v\n", cfg) 
  37.                 mux.RUnlock() 
  38.             } 
  39.             wg.Done() 
  40.         }() 
  41.     } 
  42.  
  43.     wg.Wait() 

通過上面的代碼,我們做了兩處改動。第一處改動在寫數據前通過 mux.Lock() 加了一把鎖;第二處改動在讀數據前通過 mux.RLock() 加了一把讀鎖。

運行上述代碼看一下結果: 

  1. F:\hello>go run --race main.go 
  2. &main.Config{a:[]int{512, 513, 514, 515, 516, 517}} 
  3. &main.Config{a:[]int{512, 513, 514, 515, 516, 517}} 
  4. &main.Config{a:[]int{513, 514, 515, 516, 517, 518}} 
  5. &main.Config{a:[]int{513, 514, 515, 516, 517, 518}} 
  6. &main.Config{a:[]int{513, 514, 515, 516, 517, 518}} 
  7. &main.Config{a:[]int{513, 514, 515, 516, 517, 518}} 
  8. &main.Config{a:[]int{514, 515, 516, 517, 518, 519}} 
  9. [...] 

這次達到了我們的預期并且也沒有產生數據競爭。

使用 atomic 解決數據競爭 

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "sync" 
  6.     "sync/atomic" 
  7.  
  8. type Config struct { 
  9.     a []int 
  10.  
  11. func main() { 
  12.     var v atomic.Value 
  13.  
  14.     // 寫入數據 
  15.     go func() { 
  16.         var i int 
  17.         for { 
  18.             i++ 
  19.             cfg := Config{ 
  20.                 a: []int{i, i + 1, i + 2, i + 3, i + 4, i + 5}, 
  21.             } 
  22.             v.Store(cfg) 
  23.         } 
  24.     }() 
  25.  
  26.     // 讀取數據 
  27.     var wg sync.WaitGroup 
  28.     for n := 0; n < 4; n++ { 
  29.         wg.Add(1) 
  30.         go func() { 
  31.             for n := 0; n < 100; n++ { 
  32.                 cfg := v.Load() 
  33.                 fmt.Printf("%#v\n", cfg) 
  34.             } 
  35.             wg.Done() 
  36.         }() 
  37.     } 
  38.  
  39.     wg.Wait() 

這里我們使用了 atomic 包,通過運行我們發現,也同樣達到了我們期望的結果: 

  1. [...] 
  2. main.Config{a:[]int{219142, 219143, 219144, 219145, 219146, 219147}} 
  3. main.Config{a:[]int{219491, 219492, 219493, 219494, 219495, 219496}} 
  4. main.Config{a:[]int{219826, 219827, 219828, 219829, 219830, 219831}} 
  5. main.Config{a:[]int{219948, 219949, 219950, 219951, 219952, 219953}} 

從生成的輸出結果而言,看起來使用 atomic 包的解決方案要快得多,因為它可以生成更高的數字序列。為了更加嚴謹的證明這個結果,我們下面將對這兩個程序進行基準測試。

性能分析

一個 benchmark 應該根據被測量的內容來解釋。因此,我們假設之前的程序,有一個不斷存儲新配置的 數據寫入器,同時也有多個不斷讀取配置的 數據讀取器。為了涵蓋更多潛在的場景,我們還將包括一個只有 數據讀取器 的 benchmark,假設 Config 不經常改變。

下面是部分 benchmark 的代碼: 

  1. func BenchmarkMutexMultipleReaders(b *testing.B) { 
  2.     var lastValue uint64 
  3.     var mux sync.RWMutex 
  4.     var wg sync.WaitGroup 
  5.  
  6.     cfg := Config{ 
  7.         a: []int{0, 0, 0, 0, 0, 0}, 
  8.     } 
  9.  
  10.     for n := 0; n < 4; n++ { 
  11.         wg.Add(1) 
  12.  
  13.         go func() { 
  14.             for n := 0; n < b.N; n++ { 
  15.                 mux.RLock() 
  16.                 atomic.SwapUint64(&lastValue, uint64(cfg.a[0])) 
  17.                 mux.RUnlock() 
  18.             } 
  19.             wg.Done() 
  20.         }() 
  21.     } 
  22.  
  23.     wg.Wait() 

執行上面的測試代碼后我們可以得到如下的結果: 

  1. name                              time/op 
  2. AtomicOneWriterMultipleReaders-4  72.2ns ± 2% 
  3. AtomicMultipleReaders-4           65.8ns ± 2% 
  4.  
  5. MutexOneWriterMultipleReaders-4    717ns ± 3% 
  6. MutexMultipleReaders-4             176ns ± 2% 

基準測試證實了我們之前看到的性能情況。為了了解 mutex 的瓶頸到底在哪里,我們可以在啟用 tracer 的情況下重新運行程序。

goroutines 運行時不間斷,能夠完成任務。對于帶有 mutex 的程序的配置文件,得到的結果那是完全不同的。

現在運行時間相當零碎,這是由于停放 goroutine 的 mutex 造成的。這一點可以從 goroutine 的概覽中得到證實,其中顯示了同步時被阻塞的時間。

屏蔽時間大概占到三分之一的時間,這一點可以從下面的 block profile 的圖中詳細看到。 

在這種情況下,atomic 包肯定會帶來優勢。但是,在某些方面可能會降低性能。例如,如果你要存儲一張大地圖,每次更新地圖時都要復制它,這樣效率就很低。

via: https://medium.com/a-journey-with-go/go-how-to-reduce-lock-contention-with-the-atomic-package-ba3b2664b549

作者:Vincent Blanchon 譯者:double12gzh 校對:lxbwolf

責任編輯:未麗燕 來源: Go語言中文網
相關推薦

2023-12-20 09:50:53

數據庫架構

2023-12-01 08:54:50

Java原子類型

2015-03-27 18:01:58

云計算SaaS應用渠道沖突

2023-07-05 08:18:54

Atomic類樂觀鎖悲觀鎖

2017-03-07 09:17:51

AtomicDocker遠程

2017-11-16 16:15:28

Await開發嵌套

2016-10-17 13:33:26

原子主機AnsibleCockpit

2018-09-12 15:38:42

Javaatomic編程

2015-10-23 17:29:24

AtomicOpenStack 應用部署

2025-05-13 08:00:00

2025-04-03 08:10:00

網絡IP沖突抓包

2011-04-11 11:32:29

Oracle分區表磁盤IO沖突

2011-03-15 15:47:04

MySQL鎖競爭

2011-03-07 09:05:49

鎖競爭MySQL等待時間

2022-06-23 09:00:00

JavaScriptHTML應用程序

2022-03-14 18:18:11

橫向攻擊網絡攻擊

2018-05-07 13:42:52

LinuxnpmNodeJS

2021-09-03 08:21:20

前端代碼模塊

2024-10-07 10:07:31

2024-04-01 05:10:00

Redis數據庫分布式鎖
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久久精品视频免费 | 国产成人精品a视频 | 精品欧美一区二区三区久久久小说 | 国产精品一区二区视频 | 毛片1| 亚洲国产精品成人无久久精品 | 欧美激情视频一区二区三区在线播放 | 蜜桃视频在线观看免费视频网站www | 免费的av网站 | 国产欧美一区二区久久性色99 | 免费视频一区二区 | 国产精品亚洲成在人线 | 视频一区在线 | 久久av.com | 国产有码 | 欧美高清视频 | 亚洲欧美日韩精品久久亚洲区 | 午夜免费视频 | 亚洲综合无码一区二区 | 天天躁人人躁人人躁狂躁 | 日韩欧美在 | 国产免费麻豆视频 | 午夜激情免费 | www四虎com| 精品国产乱码一区二区三区a | 中文字幕国产一区 | 国产精品久久久久久久久久三级 | 亚洲久久一区 | 免费在线观看一区二区 | 国产精品免费看 | 日韩一区二区三区视频在线播放 | 九九伦理电影 | 黄色片免费看视频 | 麻豆亚洲 | 国内精品一区二区 | 国产精品a久久久久 | 欧美a∨| 亚洲视频一区二区三区 | 天天干夜夜操 | 欧美日韩国产高清 | 国产精品一区二区av |