1. 程式人生 > 實用技巧 >go mem

go mem

一個例子

思考這段程式碼輸出什麼?

package main

var a, b int

func f() {
	a = 1
	b = 2
}

func g() {
	println(a)
	println(b)
}

func main() {
	go f()
	g()
}

實際輸出

0
0

這段程式碼的輸出其實不固定,簡單來講就是

軟體(編譯器)或者硬體(CPU)系統可以根據對程式碼的分析結果,在一定程度上打亂程式碼的執行順序,提高CPU的利用率

Go記憶體模型兩個問題

  • Happens Before
  • 可見性

To serialize access, protect the data with channel operations or other synchronization primitives such as those in the sync and sync/atomic packages.

Happens Before

Within a single goroutine, reads and writes must behave as if they executed in the order specified by the program. That is, compilers and processors may reorder the reads and writes executed within a single goroutine only when the reordering does not change the behavior within that goroutine as defined by the language specification. Because of this reordering, the execution order observed by one goroutine may differ from the order perceived by another. For example, if one goroutine executes a = 1; b = 2;, another might observe the updated value of b before the updated value of a.

編譯器重排

X = 0
for i in range(100):
    X = 1
    print X

自動優化

X = 1
for i in range(100):
    print X

但是問題是如果我們兩個goroutine執行,導致X = 0就會出現問題了,所以通過多個goroutine訪問資料,需要序列化訪問。

happens before定義

To specify the requirements of reads and writes, we define happens before, a partial order on the execution of memory operations in a Go program. If event e1 happens before event e2, then we say that e2 happens after e1. Also, if e1 does not happen before e2 and does not happen after e2, then we say that e1 and e2 happen concurrently.

機器字

Reads and writes of values larger than a single machine word behave as multiple machine-word-sized operations in an unspecified order.
32 位系統和 64 位的系統,cpu 在執行一條指令的時候對於單個機器字長的的資料的寫入可以保證是原子的,對於 32 位的就是 4 個位元組,對於 64 位的就是 8 個位元組,對於在 32 位情況下去寫入一個 8 位元組的資料時就需要執行兩次寫入操作,這兩次操作之間就沒有原子性,那就可能出現先寫入後半部分的資料再寫入前半部分,或者是寫入了一半資料然後寫入失敗的情況。

同步

初始化

Program initialization runs in a single goroutine, but that goroutine may create other goroutines, which run concurrently.
If a package p imports package q, the completion of q‘s init functions happens before the start of any of p‘s.
The start of the function main.main happens after all init functions have finished.

goroutine的建立

The go statement that starts a new goroutine happens before the goroutine’s execution begins.

goroutine的銷燬

The exit of a goroutine is not guaranteed to happen before any event in the progra.

記憶體重排

  • C1 執行 a = 1 將 store buffer 中 a 的值寫為 1
  • C1 執行 b = 2 將 store buffer 中 b 的值寫為 2, 然後由於某種原因將 b 的值寫入到了記憶體中
  • C2 去讀取 b 的值,發現快取中沒有,就去記憶體中讀,這時候 print 出來 2
  • C2 去讀取 a 的值,發現快取中沒有,就去記憶體中讀,這時候 print 出來 0

ref

http://lailin.xyz/post/go-training-week3-go-memory-model.html