大多數時候,我們對并發的直覺都是錯誤的,特别是在多核處理器上更為明顯。這也是高并發程式難寫的原因。
人類想了種種辦法來降低程式員犯錯誤的機率,發明了很多方法。甚至在有些語言裡,幹脆就不支援多線程了,比如基于 NodeJs 開發的伺服器。
但在 Golang 裡我們無法逃避這種并發競争。
1. 競争例子
下面這個例子開啟了 100 個 goroutine,讀寫全局變量 a.
// demo01.go
package main
import (
"fmt"
"sync"
)
var a int
func run() {
a++
}
func main() {
var wg sync.WaitGroup
num := 100
wg.Add(num)
for i := 0; i < num; i++ {
go func() {
run()
wg.Done()
}()
}
wg.Wait()
fmt.Printf("a = %d\n", a)
}
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5CM1QTM3ITMlhDO4IjYzgDMzYzX2UjNxETM2IzLchDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
圖1 運作結果
上面的程式反複運作了幾次,發現有幾次結果不太一樣,我們期望的結果應該是 100。
原因就在于 a++ 這一行語句不是原子執行的。
2. 競争檢測
golang 提供了競争檢測工具,用來輔助我們發現程式種出現的資料競争。隻要在指令後面加上 -race 選項就可以了:
$ go test -race mypkg // 測試包
$ go run -race mysrc.go // 編譯和運作程式
$ go build -race mycmd // 建構程式
$ go install -race mypkg // 安裝程式
在我們這個例子中,你可以使用下面的方法:
$ go run -race demo01.go
或者你也可以使用
$ go build -race demo01.go
$ ./demo01
圖2 使用競争檢漏工具
競争檢測器內建在go工具鍊中。當使用了-race作為指令行參數後,編譯器會插樁代碼,使得所有代碼在通路記憶體時,會記錄通路時間和方法。同時運作時庫會觀察對共享變量的未同步通路。
3. 使用互斥鎖解決競争問題
// demo02.go
package main
import (
"fmt"
"sync"
)
var a int
var mu sync.Mutex
func run() {
mu.Lock()
defer mu.Unlock()
a++
}
func main() {
var wg sync.WaitGroup
num := 100
wg.Add(num)
for i := 0; i < num; i++ {
go func() {
run()
wg.Done()
}()
}
wg.Wait()
fmt.Printf("a = %d\n", a)
}
接下來再運作
go run -race demo02.go
,就沒有問題了,而且每次結果都是 100.
4. 總結
- 學會使用競争檢測工具