天天看點

079-競争檢測

大多數時候,我們對并發的直覺都是錯誤的,特别是在多核處理器上更為明顯。這也是高并發程式難寫的原因。

人類想了種種辦法來降低程式員犯錯誤的機率,發明了很多方法。甚至在有些語言裡,幹脆就不支援多線程了,比如基于 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)
}      
079-競争檢測

圖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      
079-競争檢測

圖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.

079-競争檢測

4. 總結

  • 學會使用競争檢測工具