概念簡介
在前麵的例子中,我們看到了如何使用原子操作來管理簡單的計數器。
對於更加複雜的情況,我們可以使用一個互斥鎖
來在 Go 協程間安全的訪問數據。
例程代碼
package main
import (
"fmt"
"math/rand"
"runtime"
"sync"
"sync/atomic"
"time"
)
func main() {
// 在我們的例子中,`state` 是一個 map。
var state = make(map[int]int)
// 這裏的 `mutex` 將同步對 `state` 的訪問。
var mutex = &sync.Mutex{}
// we'll see later, `ops` will count how many
// operations we perform against the state.
// 為了比較基於互斥鎖的處理方式和我們後麵將要看到的其他
// 方式,`ops` 將記錄我們對 state 的操作次數。
var ops int64 = 0
// 這裏我們運行 100 個 Go 協程來重複讀取 state。
for r := 0; r < 100; r++ {
go func() {
total := 0
for {
// 每次循環讀取,我們使用一個鍵來進行訪問,
// `Lock()` 這個 `mutex` 來確保對 `state` 的
// 獨占訪問,讀取選定的鍵的值,`Unlock()` 這個
// mutex,並且 `ops` 值加 1。
key := rand.Intn(5)
mutex.Lock()
total += state[key]
mutex.Unlock()
atomic.AddInt64(&ops, 1)
// 為了確保這個 Go 協程不會在調度中餓死,我們
// 在每次操作後明確的使用 `runtime.Gosched()`
// 進行釋放。這個釋放一般是自動處理的,像例如
// 每個通道操作後或者 `time.Sleep` 的阻塞調用後
// 相似,但是在這個例子中我們需要手動的處理。
runtime.Gosched()
}
}()
}
// 同樣的,我們運行 10 個 Go 協程來模擬寫入操作,使用
// 和讀取相同的模式。
for w := 0; w < 10; w++ {
go func() {
for {
key := rand.Intn(5)
val := rand.Intn(100)
mutex.Lock()
state[key] = val
mutex.Unlock()
atomic.AddInt64(&ops, 1)
runtime.Gosched()
}
}()
}
// 讓這 10 個 Go 協程對 `state` 和 `mutex` 的操作
// 運行 1 s。
time.Sleep(time.Second)
// 獲取並輸出最終的操作計數。
opsFinal := atomic.LoadInt64(&ops)
fmt.Println("ops:", opsFinal)
// 對 `state` 使用一個最終的鎖,顯示它是如何結束的。
mutex.Lock()
fmt.Println("state:", state)
mutex.Unlock()
}
執行&輸出
# 運行這個程序,顯示我們對已進行了同步的 `state` 執行了
# 3,500,000 次操作。
$ go run mutexes.go
ops: 3598302
state: map[1:38 4:98 2:23 3:85 0:44]
# 接下來我們將看一下隻使用 Go 協程和通道是如何實現
# 相同的狀態控製任務的。
課程導航
學習上一篇:Go語言教程:原子計數器 學習下一篇:Go語言教程:狀態協程
相關資料
本例程github源代碼:https://github.com/xg-wang/gobyexample/tree/master/examples/mutexes