gopl-zh.github.com/ch9/ch9-04.md
2016-01-21 10:44:23 +08:00

46 lines
3.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 9.4. 內存同步
你可能比較糾結爲什麽Balance方法需要用到互斥條件無論是基於channel還是基於互斥量。畢竟和存款不一樣它隻由一個簡單的操作組成所以不會碰到其它goroutine在其執行"中"執行其它的邏輯的風險。這里使用mutex有兩方面考慮。第一Balance不會在其它操作比如Withdraw“中間”執行。第二(更重要)的是"同步"不僅僅是一堆goroutine執行順序的問題同樣也會涉及到內存的問題。
在現代計算機中可能會有一堆處理器,每一個都會有其本地緩存(local cache)。爲了效率對內存的寫入一般會在每一個處理器中緩衝併在必要時一起flush到主存。這種情況下這些數據可能會以與當初goroutine寫入順序不同的順序被提交到主存。像channel通信或者互斥量操作這樣的原語會使處理器將其聚集的寫入flush併commit這樣goroutine在某個時間點上的執行結果才能被其它處理器上運行的goroutine得到。
考慮一下下面代碼片段的可能輸出:
```go
var x, y int
go func() {
x = 1 // A1
fmt.Print("y:", y, " ") // A2
}()
go func() {
y = 1 // B1
fmt.Print("x:", x, " ") // B2
}()
```
因爲兩個goroutine是併發執行併且訪問共享變量時也沒有互斥會有數據競爭所以程序的運行結果沒法預測的話也請不要驚訝。我們可能希望它能夠打印出下面這四種結果中的一種相當於幾種不同的交錯執行時的情況
```
y:0 x:1
x:0 y:1
x:1 y:1
y:1 x:1
```
第四行可以被解釋爲執行順序A1,B1,A2,B2或者B1,A1,A2,B2的執行結果。
然而實際的運行時還是有些情況讓我們有點驚訝:
```
x:0 y:0
y:0 x:0
```
但是根據所使用的編譯器CPU或者其它很多影響因子這兩種情況也是有可能發生的。那麽這兩種情況要怎麽解釋呢
在一個獨立的goroutine中每一個語句的執行順序是可以被保證的也就是説goroutine是順序連貫的。但是在不使用channel且不使用mutex這樣的顯式同步操作時我們就沒法保證事件在不同的goroutine中看到的執行順序是一致的了。盡管goroutine A中一定需要觀察到x=1執行成功之後才會去讀取y但它沒法確保自己觀察得到goroutine B中對y的寫入所以A還可能會打印出y的一個舊版的值。
盡管去理解併發的一種嚐試是去將其運行理解爲不同goroutine語句的交錯執行但看看上面的例子這已經不是現代的編譯器和cpu的工作方式了。因爲賦值和打印指向不同的變量編譯器可能會斷定兩條語句的順序不會影響執行結果併且會交換兩個語句的執行順序。如果兩個goroutine在不同的CPU上執行每一個核心有自己的緩存這樣一個goroutine的寫入對於其它goroutine的Print在主存同步之前就是不可見的了。
所有併發的問題都可以用一致的、簡單的旣定的模式來規避。所以可能的話將變量限定在goroutine內部如果是多個goroutine都需要訪問的變量使用互斥條件來訪問。