這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Dave 總是會給我們帶來這種很淺顯有趣,又意義深刻的文章。原文在此:Ice cream makers and data races。
————翻譯分隔線————
冰激淋製造商和資料競態
Dave Cheney
這是一篇關於資料競態的文章。本文的相關代碼在 Github 上:github.com/davecheney/benandjerry。
這個例子類比了兩個冰激淋製造商 Ben 和 Jerry 隨機接待他們的客戶。
package mainimport "fmt"type IceCreamMaker interface { // 向客戶說 Hello Hello()}type Ben struct { name string}func (b *Ben) Hello() { fmt.Printf("Ben says, \"Hello my name is %s\"\n", b.name)}type Jerry struct { name string}func (j *Jerry) Hello() { fmt.Printf("Jerry says, \"Hello my name is %s\"\n", j.name)}func main() { var ben = &Ben{"Ben"} var jerry = &Jerry{"Jerry"} var maker IceCreamMaker = ben var loop0, loop1 func() loop0 = func() { maker = ben go loop1() } loop1 = func() { maker = jerry go loop0() } go loop0() for { maker.Hello() }}
這是資料競態,傻瓜
大多數程式員應當很容易就看出在這個程式裡存在資料競態。
迴圈函數在沒有加鎖的情況下修改了 maker 的值,當主函數中的迴圈調用 maker.Hello() 的時候,無法明確 Hello 的哪個實現將被調用。
一些程式員可能對此並不在意,Ben 或者 Jerry 來招待客戶,到底是哪個無所謂。
讓我們運行這個代碼,看看會發生什麼。
% env GOMAXPROCS=2 go run main.go...Ben says, "Hello my name is Ben"Jerry says, "Hello my name is Jerry"Jerry says, "Hello my name is Jerry"Ben says, "Hello my name is Jerry"Ben says, "Hello my name is Ben"...
等等,這是什麼!Ben 有時會認為自己是 Jerry。這怎麼可能?
介面值
理解這個競態的關鍵是理解介面值在記憶體中的表現形式。
介面在概念上是一個具有兩個欄位的結構體。
如果用 Go 來描述介面,它看起來會是這樣。
type interface struct { Type uintptr // 指向實現介面的類型的指標 Data uintptr // 持有實現了介面的接收者的資料}
Type 指向實現了用來描述這個介面的值的類型的結構體。Data 指向了值的實現本身。Data 的內容作為被呼叫者法的接收者,通過介面傳遞。
通過語句 maker IceCreamMaker = ben,編譯器會產生代碼做以下事情。
介面的 Type 欄位被設定指向 *Ben 類型的定義,而 Data 欄位儲存了 ben 的副本,一個指向 Ben 的值的指標。
當語句 loop1() 執行的時候,maker = jerry 更新了介面值中的兩個欄位。
Type 現在指向 *Jerry 的定義,而 Data 儲存了指向 Jerry 的指標。
Go 記憶體模型說向一個機器字寫入是原子的,但是介面有兩個字大小。當介面值被修改的時候,另外一個 goroutine 可能會讀取其內容。在這個例子中,可能會發生
因此 Jerry 的 Hello() 函數調用了 ben 作為接收者。
總結
沒有叫做安全資料競態的東西。你的程式要麼沒有資料競態,要麼它的操作無法定義。
在這個例子中,Ben 和 Jerry 的記憶體布局恰好匹配,因此在某些情況下看起來無害。設想如果它們的記憶體布局不同,會是怎麼樣的一個混亂世界(這作為練習留給了讀者)。
Go 競態檢測器能偵測到這個錯誤,以及其他可能,只需要簡單的在調用 go test、build 或 install 命令時添加 -race 標識。
附加問題
在例子代碼裡,Hello 方法被定義為 Ben 或 Jerry 的指標接收者。如果替代為在 Ben 或 Jerry 的值上定義的方法,能解決這個資料競態嗎?
擴充閱讀
Russ Cox 關於 Go 介面的,在你閱讀後,還應當瞭解下 Russ 撰寫的關於這個問題的解釋。
Go 競態檢測器的博文(中文翻譯)。