競態條件的賦值 Go 譯文之競態檢測器 race

2021-10-12 14:31:43 字數 3189 閱讀 1290

譯者前言

第三篇 go 官方部落格譯文,主要是關於 go 內建的競態條件檢測工具。它可以有效地幫助我們檢測併發程式的正確性。使用非常簡單,只需在 go 命令加上 -race 選項即可。

本文最後介紹了兩個真實場景下的競態案例,第乙個案例相對比較簡單。重點在於第二個案例,這個案例比較難以理解,在原文的基礎上,我也簡單做了些補充,不知道是否把問題講的足夠清楚。同時,這個案例也告訴我們,任何時候我們都需要重視檢測器給我們的提示,因為一不小心,你就可能為自己留下乙個大坑。

概要在程式世界中,競態條件是一種潛伏深且很難發現的錯誤,如果將這樣的**部署線上,常會產生各種謎一般的結果。go 對併發的支援讓我們能非常簡單就寫出支援併發的**,但它並不能阻止競態條件的發生。

本文將會介紹乙個工具幫助我們實現它。

go 1.1 加入了乙個新的工具,競態檢測器,它可用於檢測 go 程式中的競態條件。當前,執行在 x86_64 處理器的 linux、mac 或 windows 下可用。

競態檢測器的實現基於 c/c++ 的 threadsanitizer 執行時庫,threadsanitier 在 googgle 已經被用在一些內部基礎庫以及 chromium上,並且幫助發現了很多有問題的**。

threadsanitier 這項技術在 2012 年 9 月被整合到了 go 上,它幫助檢測出了標準庫中的 42 個競態問題。它現在已經是 go 構建流程中的一部分,當競態條件出現,將會被它捕獲。

如何工作

競態檢測器整合在 go 工具鏈,當命令列設定了 -race 標誌,編譯器將會通過**記錄所有的記憶體訪問,何時以及如何被訪問,執行時庫也會負責監視共享變數的非同步訪問。當檢測到競態行為,警告資訊會把列印出來。(具體詳情閱讀 文章)

這樣的設計導致競態檢測只能在執行時觸發,這也意味著,真實環境下執行 race-enabled 的程式就變得非常重要,但 race-enabled 程式耗費的 cpu 和記憶體通常是正常程式的十倍,在真實環境下一直啟用競態檢測是非常不切合實際的。

是否感受到了一陣涼涼的氣息?

這裡有幾個解決方案可以嘗試。比如,我們可以在 race-enabled 的情況下執行測試,負載測試和整合測試是個不錯的選擇,它偏向於檢測**中可能存在的併發問題。另一種方式,可以利用生產環境的負載均衡,選擇一台服務部署啟動競態檢測的程式。

開始使用

競態檢測器已經整合到 go 工具鏈中了,只要設定 -race 標誌即可啟用。命令列示例如下:

$ go test -race mypkg

$ go run -race mysrc.go

$ go build -race mycmd

$ go install -race mypkg

通過具體案例體驗下,安裝執行乙個命令,步驟如下:

$ go get -race golang.org/x/blog/support/racy

$ racy

接下來,我們介紹 2 個實際的案例。

案例 1:timer.reset

這是乙個由競態檢測器發現的真實的 bug,這裡將演示的是它的乙個簡化版本。我們通過 timer 實現隨機間隔(0-1 秒)的訊息列印,timer 會重複執行 5 秒。

首先,通過 time.afterfunc 建立 timer,定時的間隔從 randomduration 函式獲得,定時函式列印訊息,然後通過 timer 的 reset 方法重置定時器,重複利用。

func main()

io.copy(writer, tdr)

fmt.printf("file hash: %x", tdr.h.sum(nil))

某些情況下,如果沒有地方可供資料寫入,但我們還是需要計算 hash,就可以用 discard 了。

io.copy(ioutil.discard, tdr)

此時的 blackhole buffer 並非僅僅是乙個黑洞,它同時也是 io.reader 和 hash.hash 之間傳遞資料的紐帶。當多個 goroutine 併發執行檔案 hash 時,它們全部共享乙個 buffer,read 和 write 之間的資料就可能產生相應的衝突。no error 並且 no panic,但是 hash 的結果是錯的。就是如此可惡。

func (t trackdigestreader) read(p byte) (n int, err error) {

// the buffer p is blackhole

n, err = t.r.read(p)

// p may be corrupted by another goroutine here,

// between the read above and the write below

t.h.write(p[:n])

return

最終,通過為每乙個 io.discard 提供唯一的 buffer,我們解決了這個 bug,排除了共享 buffer 的競態條件。**如下:

var blackholebuf = make(chan byte, 1)

func blackhole() byte {

select {

case b :=

return b

default:

return make(byte, 8192)

func blackholeput(p byte) {

select {

case blackholebuf

default:

iouitl.go 中的 devnull readfrom 方法也做了相應修正。

func (devnull) readfrom(r io.reader) (n int64, err error) {

buf := blackhole()

defer blackholeput(buf)

readsize := 0

for {

readsize, err = r.read(buf)

// other

通過 defer 將使用完的 buffer 重新傳送至 blackholebuf,因為 channel 的 size 為 1,只能復用乙個 buffer。而且通過 select 語句,我們在沒有可用 buffer 的情況下,建立新的 buffer。

結論競態檢測器,乙個非常強大的工具,在併發程式的正確性檢測方面有著很重要的地位。它不會發出假的提示,認真嚴肅地對待它的每條警示非常必要。但它並非萬能,還是需要以你對併發特性的正確理解為前提,才能真正地發揮出它的價值。

試試吧!開始你的 go test -race。

12 競態條件 時序競態

sleep函式幾點說明 1 sleep函式作用,讓程序睡眠。2 能被訊號打斷,然後處理訊號函式以後,就不再睡眠了。直接向下執行 3 sleep函式的返回值,是剩餘的秒數 sleep是可被中斷的睡眠,但不一定睡夠100s sleep是可中斷的睡眠 void handler int num if 1 v...

linux作業系統之競態條件(時序競態)

1 時序競態 前後兩次執行同乙個程式,出現的結果不同。2 pause函式 使用該函式會造成程序主動掛起,並等待訊號喚醒,呼叫該系統呼叫的程序會處於阻塞狀態 主動放棄cpu 函式原型 int pause void 返回值為 1,並設定errno為eintr 使用pause和alarm實現sleep函式...

什麼是競態條件

1 什麼是競態條件?當兩個執行緒競爭同一資源時,如果對資源的訪問順序敏感,就稱存在競態條件。導致競態條件發生的 區稱作臨界區。在臨界區中使用適當的同步就可以避免競態條件。臨界區實現方法有兩種,一種是用synchronized,一種是用lock顯式鎖實現。2 例項 class counter 觀察執行...