Go 記憶體模型和Happens Before關係

來源:互聯網
上載者:User

Happens Before 是記憶體模型中一個通用的概念,Go 中也定義了Happens Before以及各種發生Happens Before關係的操作,因為有了這些Happens Before操作的保證,我們寫的多goroutine的程式才會按照我們期望的方式來工作。

什麼是Happens Before關係

Happens Before定義了兩個操作間的偏序關係,具有傳遞性。對於兩個操作E1和E2:

  1. 如果E1 Happens Before E2, 則E2 Happens After E1;
  2. 如果E1 Happens E2, E2 Happens Before E3,則E1 Happens E3;
  3. 如果 E1 和 E2沒有任何Happens Before關係,則說E1和E2 Happen Concurrently。

Happens Before的作用

Happens Before主要是用來保證記憶體操作的可見度。如果要保證E1的記憶體寫操作能夠被E2讀到,那麼需要滿足:

  1. E1 Happens Before E2;
  2. 其他所有針對此記憶體的寫操作,要麼Happens Before E1,要麼Happens After E2。也就是說不能存在其他的一個寫操作E3,這個E3 Happens Concurrently E1/E2。

為什麼需要定義Happens Before關係來保證記憶體操作的可見度呢?原因是沒有限制的情況下,編譯器和CPU使用的各種最佳化,會對此造成影響,具體的來說就是操作重排序和CPU CacheLine緩衝同步:

  • 操作重排序。現代CPU通常是流水線架構,且具有多個核心,這樣多條指令就可以同時執行。然而有時候出現一條指令需要等待之前指令的結果,或是其他造成指令執行需要延遲的情況。這個時候可以先執行下一條已經準備好的指令,以儘可能高效的利用CPU。操作重排序可以在兩個階段出現:
    • 編譯器指令重排序
    • CPU亂序執行
  • CPU 多核心間獨立Cache Line的同步問題。多核CPU通常有自己的一級緩衝和二級緩衝,訪問緩衝的資料很快。但是如果緩衝沒有同步到主存和其他核心的緩衝,其他核心讀取緩衝就會讀到到期的資料。

舉例來說,看一個多Goroutine的程式:

// Sample Routine 1func happensBeforeMulti(i int) {i += 2 // E1go func() { // G1 goroutine createfmt.Println(i) // E2}() // G2 goroutine destryo}

對此來講解:

  1. 如果編譯器或者CPU進行了重排序,那麼E1的指令可能在E2之後執行,從而輸出錯誤的值;
  2. 變數i被CPU緩衝到Cache Line中,E1對i的修改只改寫了Cache Line,沒有寫回主存;而E2在另外的goroutine執行,如果和E1不是在同一個核上,那麼E2輸出的就是錯誤的值。

而Happens Before關係,就是對編譯器和CPU的限制,禁止違反Happens Before關係的指令重排序及亂序執行行為,以及必要的情況下保證CacheLine的資料更新等。

Go 中定義的Happens Before保證

1) 單線程

  • 在單線程環境下,所有的運算式,按照代碼中的先後順序,具有Happens Before關係。

CPU和正確實現的編譯器,對單線程情況下的Happens Before關係,都是有保障的。這並不是說編譯器或者CPU不能做重排序,只要最佳化沒有影響到Happens Before關係就是可以的。這個依據在於分析資料的依賴性,資料沒有依賴的操作可以重排序。

比如以下程式:

// Sample Routine 2func happsBefore(i int, j int) {i += 2             // E1j += 10            // E2fmt.Println(i + j) //E3}

E1和E2之間,執行順序是沒有關係的,只要保證E3沒有被亂序到E1和E2之前執行就可以。

2) Init 函數

  • 如果包P1中匯入了包P2,則P2中的init函數Happens Before 所有P1中的操作
  • main函數Happens After 所有的init函數

3) Goroutine

  • Goroutine的建立Happens Before所有此Goroutine中的操作
  • Goroutine的銷毀Happens After所有此Goroutine中的操作

我們上面提到的Sample Routine 1,按照規則1, E1 Happens before G1,按照本規則,G1 Happens Before E2,從而E1 Happens Before E2。

4) Channel

  • 對一個元素的send操作Happens Before對應的receive 完成操作
  • 對channel的close操作Happens Before receive 端的收到關閉通知操作
  • 對於Unbuffered Channel,對一個元素的receive 操作Happens Before對應的send完成操作
  • 對於Buffered Channel,假設Channel 的buffer 大小為C,那麼對第k個元素的receive操作,Happens Before第k+C個send完成操作。可以看出上一條Unbuffered Channel規則就是這條規則C=0時的特例

首先注意這裡面,send和send完成,這是兩個事件,receive和receive完成也是兩個事件。

然後,Buffered Channel這裡有個坑,它的Happens Before保證比UnBuffered 弱,這個弱只在【在receive之前寫,在send之後讀】這種情況下有問題。而【在send之前寫,在receive之後讀】,這樣用是沒問題的,這也是我們通常寫程式常用的模式,千萬注意這裡不要弄錯!

// Channel routine 1var c = make(chan int)var a stringfunc f() {a = "hello, world"<-c}func main() {go f()c <- 0print(a)}


// Channel routine 2var c = make(chan int, 10)var a stringfunc f() {a = "hello, world"<-c}func main() {go f()c <- 0print(a)}


// Channel routine 3var c = make(chan int, 10)var a stringfunc f() {a = "hello, world"c <- 0}func main() {go f()<-cprint(a)}

比如上面這三個程式,使用channel來做同步,程式1和程式3是能夠保證Happens Before關係的,程式2則不能夠,也就是程式可能不會按照期望輸出"hello, world"。

5) Lock

Go裡面有Mutex和RWMutex兩種鎖,RWMutex除了支援互斥的Lock/Unlock,還支援共用的RLock/RUnlock。

  • 對於一個Mutex/RWMutex,設n < m,則第n個Unlock操作Happens Before第m個Lock操作。
  • 對於一個RWMutex,存在數值n,RLock操作Happens After 第n個UnLock,其對應的RUnLockHappens Before 第n+1個Lock操作。

簡單理解就是這一次的Lock總是Happens After上一次的Unlock,讀寫鎖的RLock HappensAfter上一次的UnLock,其對應的RUnlock Happens Before 下一次的Lock。

6) Once

once.Do中執行的操作,Happens Before 任何一個once.Do調用的返回


如果你對JVM的記憶體模型及定義的Happens Before關係都有所瞭解,那麼這裡對Go的記憶體模型的講解與之非常類似,理解起來會非常容易。太陽底下無新鮮事,瞭解了一種語言的記憶體模型設計,其他類似的語言也就都可以很容易的理解了。如果是前端或者使用node的程式員,那麼你壓根就不需要清楚這些,畢竟始終只有一個線程在跑是吧。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.