如果你對通過內存訪問同步處理并發的語言很熟悉,那么你可能會立即明白Mutex的使用方法。如果你沒有這樣的經驗,沒關系,Mutex很容易理解。Mutex代表"mutual exclusion(互斥)"。互斥提供了一種并發安全的方式來表示對共享資源訪問的獨占。下面是一個簡單的兩個goroutine,它們試圖增加和減少一個公共值;,并使用Mutex來同步訪問:
```
var count int
var lock sync.Mutex
increment := func() {
lock.Lock() // 1
defer lock.Unlock() // 2
count++
fmt.Printf("Incrementing: %d\n", count)
}
decrement := func() {
lock.Lock() // 1
defer lock.Unlock() // 2
count--
fmt.Printf("Decrementing: %d\n", count)
}
// Increment
var arithmetic sync.WaitGroup
for i := 0; i <= 5; i++ {
arithmetic.Add(1)
go func() {
defer arithmetic.Done()
increment()
}()
}
// Decrement
for i := 0; i <= 5; i++ {
arithmetic.Add(1)
go func() {
defer arithmetic.Done()
decrement()
}()
}
arithmetic.Wait()
fmt.Println("Arithmetic complete.")
```
1. 在這里,我們要求獨占使用關鍵部分 - 在這種情況下,count變量由互斥鎖保護。
2. 這里表明我們已經完成了對共享部分的鎖定。
這會輸出:
```
Decrementing: -1
Incrementing: 0
Decrementing: -1
Incrementing: 0
Decrementing: -1
Decrementing: -2
Decrementing: -3
Incrementing: -2
Decrementing: -3
Incrementing: -2
Incrementing: -1
Incrementing: 0 Arithmetic complete.
```
你會注意到我們總是使用defer在延遲聲明中調用解鎖。 使用互斥鎖時,這是一個非常常見的習慣用法,以確保調用始終執行,即使在發生恐慌時也是如此。否則一旦未能解除鎖定,可能會導致你的程序陷入死鎖。
被鎖定部分是程序的性能瓶頸,進入和退出鎖定的成本有點高,因此人們通常盡量減少鎖定涉及的范圍。
可能在多個并發進程之間共享的內存并不是都要讀取和寫入,出于這樣的考慮,你可以使用另一個類型的互斥鎖:sync.RWMutex。
sync.RWMutex與Mutex在概念上是一樣的:它保護對內存的訪問;不過,RWMutex可以給你更多地控制方式。 你可以請求鎖定進行讀取,在這種情況下,你將被授予讀取權限,除非鎖定正在進行寫入操作。 這意味著,只要沒有別的東西占用寫操作,任意數量的讀取者就可以進行讀取操作。 下面是一個演示生產者的示例:
```
producer := func(wg *sync.WaitGroup, l sync.Locker) { //1
defer wg.Done()
for i := 5; i > 0; i-- {
l.Lock()
l.Unlock()
time.Sleep(1) //2
}
}
observer := func(wg *sync.WaitGroup, l sync.Locker) {
defer wg.Done()
l.Lock()
defer l.Unlock()
}
test := func(count int, mutex, rwMutex sync.Locker) time.Duration {
var wg sync.WaitGroup
wg.Add(count + 1)
beginTestTime := time.Now()
go producer(&wg, mutex)
for i := count; i > 0; i-- {
go observer(&wg, rwMutex)
}
wg.Wait()
return time.Since(beginTestTime)
}
tw := tabwriter.NewWriter(os.Stdout, 0, 1, 2, ' ', 0)
defer tw.Flush()
var m sync.RWMutex
fmt.Fprintf(tw, "Readers\tRWMutext\tMutex\n")
for i := 0; i < 20; i++ {
count := int(math.Pow(2, float64(i)))
fmt.Fprintf(
tw, "%d\t%v\t%v\n", count,
test(count, &m, m.RLocker()), test(count, &m, &m),
)
}
```
1. producer函數的第二個參數是類型sync.Locker。 該接口有兩種方法,鎖定和解鎖,互斥和RWMutex類型都適用。
2. 在這里,我們讓producer休眠一秒鐘,使其不那么活躍。
這會輸出:
```
Readers RWMutext Mutex
1 5ms 5ms
2 5ms 5ms
4 5ms 5ms
8 5ms 5ms
16 5ms 5ms
32 5ms 5ms
64 5ms 5ms
128 5ms 5ms
256 5ms 5ms
512 5ms 5ms
1024 5ms 5ms
2048 5ms 5ms
4096 6ms 7ms
8192 8ms 8ms
16384 7ms 8ms
32768 9ms 11ms
65536 12ms 15ms
131072 29ms 31ms
262144 61ms 68ms
524288 121ms 137ms
```
你可以通過這個例子看到,RWMutext在大量級上相對于Mutex是有性能優勢的,不過這同樣取決于你在鎖住的部分做了什么。通常建議在邏輯上合理的情況下使用RWMutex而不是Mutex。
* * * * *
學識淺薄,錯誤在所難免。我是長風,歡迎來Golang中國的群(211938256)就本書提出修改意見。
- 前序
- 誰適合讀這本書
- 章節導讀
- 在線資源
- 第一章 并發編程介紹
- 摩爾定律,可伸縮網絡和我們所處的困境
- 為什么并發編程如此困難
- 數據競爭
- 原子性
- 內存訪問同步
- 死鎖,活鎖和鎖的饑餓問題
- 死鎖
- 活鎖
- 饑餓
- 并發安全性
- 優雅的面對復雜性
- 第二章 代碼建模:序列化交互處理
- 并發與并行
- 什么是CSP
- CSP在Go中的衍生物
- Go的并發哲學
- 第三章 Go的并發構建模塊
- Goroutines
- sync包
- WaitGroup
- Mutex和RWMutex
- Cond
- Once
- Pool
- Channels
- select語句
- GOMAXPROCS
- 結論
- 第四章 Go的并發編程范式
- 訪問范圍約束
- fo-select循環
- 防止Goroutine泄漏
- or-channel
- 錯誤處理
- 管道
- 構建管道的最佳實踐
- 便利的生成器
- 扇入扇出
- or-done-channel
- tee-channel
- bridge-channel
- 隊列
- context包
- 小結
- 第五章 可伸縮并發設計
- 錯誤傳遞
- 超時和取消
- 心跳
- 請求并發復制處理
- 速率限制
- Goroutines異常行為修復
- 本章小結
- 第六章 Goroutines和Go運行時
- 任務調度