本文講述在何種情況下,一個協程(goroutine)中寫入的變量值可被另一個協程中觀察到。
先發生(happens before)
讓我們來理清下時間的發生順序。
如果事件 e1在事件 e2 之前發生,那麼事件 e2 在事件 e1 之後發生。同樣,如果 e1 既不在事件 e2 之前也不在 e2 之後發生,那麼事件 e1 和 e2 同時發生。
規則1: 在單個協程中,事件的發生順序就是程序語句的先後順序。
當滿足以下條件時,對變量 v 的讀取 r 可以觀察到對 v 的寫入 w:
- r 不在 w 之前發生;
- 在 w 之後 r 之前,沒有其他的寫入。
換句話說:
- w 在 r 之前發生;
- 其他的寫入要麼發生在 w 之前,要麼發生在 r 之後。
單個協程下這兩種表述沒什麼不同,但在多協程下需要通過同步來建立先發生條件以保證觀察到其他協程對共享變量的寫入。
同步(synchronization)
(1)初始化(initialization)
規則2: 若包 p 導入了包 q,則 q 的初始化方法(init)先發生於 p 的。主方法(main.main)發生在所有 init 方法完成之後。
(2)協程創建(channel creation)
規則3: go 語句新起一個協程先發生於協程執行。
(3)協程銷毀(channel destruction)
協程的銷毀不保證發生在任何事件之前。
(4)通道通信(channel communication)
通道是Go中最常用的同步方式,對於通道的事件順序有如下規則:
規則4: 發送消息到 channel 先發生於對應的接收完成。
規則5: 通道的關閉先發生於因通道關閉而接收到零值。
規則6: 從無緩衝通道接收消息先發生於發送消息完成。
規則7: 對於容量為 C 的緩衝通道,第 k 次接收消息先發生於第 k+C 次發送消息完成。
(5)鎖(locks)
當使用鎖進行同步時,需要注意鎖是一加一放的,不會先後加兩次鎖,也不會先後釋放兩次鎖。
規則8: 對於任意類型為 sync.Mutex 或 sync.RWMutex 的變量 l,以及 n < m,第 n 次調用 l.Unlock() 先發生於第 m 次調用 l.Lock()。
(6)僅執行一次(once)
once.Do(f) 能保證函數 f() 能在多協程環境下僅被調用一次。是 Go 中實現單例常用方法。
規則9: 對函數 f() 的單次調用先發生(先返回)於 once.Do(f) 返回。