譯者前言
第三篇 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 觀察執行...