【翻譯】go memory model

https://studygolang.com/articles/819 原文連接golang

Introduction
The Go memory model specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine.express

Go內存模型限定了一些條件 知足這些條件 才能讓變量 安全地在不一樣的goroutine之間讀寫安全

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.app

a = 1;
b = 2;
若是隻有一個goroutine 讀寫行爲和它們在程序中的位置一致 就是說 編譯器和處理器只能在保證讀寫在程序中的正確行爲時 好比讀寫順序 纔有可能對讀寫進行優化 舉例來講 在一個goroutine中執行 另外一個goroutine中可能會在a以前察覺到b的更新操做ide

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 e1happens 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 e2happen concurrently.函數

爲了定義讀寫的需求 咱們給Go程序的內存操做 定義了一個偏序「發生在X以前」 若是時間e1發生在e2以前 那麼咱們認爲e2事件出如今e1後 一樣的 若是e1不出如今e2以前 而且又不出如今e2以後 那麼咱們認爲e1和e2是並行的oop

Within a single goroutine, the happens-before order is the order expressed by the program.優化

在同一個goroutine中 「發生在X以前」這個次序和程序中表達出來的次序是一致的ui

A read r of a variable v is allowed to observe a write w to v if both of the following hold:this

對v的寫操做w 若是想被v的讀操做r觀察到 須要知足下面兩個條件:

r does not happen before w. r不發生在w以前
There is no other write w' to v that happens after w but before r. 沒有其它的寫操做w` 發生在w以後 r以前
To guarantee that a read r of a variable v observes a particular write w to v, ensure that w is the only write r is allowed to observe. That is, r is guaranteed to observe w if both of the following hold:

想要保證讀操做r 能夠觀察到特定的w寫操做 須要確保w是r惟一能夠觀察到的寫操做 也就是說 只有知足下面條件後 才得以保證r觀察到w

w happens before r. w發生在r以前
Any other write to the shared variable v either happens before w or after r. 任何其它對v的寫 要麼發生在w以前 要麼在r以後
This pair of conditions is stronger than the first pair; it requires that there are no other writes happening concurrently with w or r.

這兩個條件要比以前的兩個條件更加嚴苛 它須要沒有其它的寫操做和w或者r是並行發生的

Within a single goroutine, there is no concurrency, so the two definitions are equivalent: a read r observes the value written by the most recent write w to v. When multiple goroutines access a shared variable v, they must use synchronization events to establish happens-before conditions that ensure reads observe the desired writes.

在單一goroutine環境下 沒有並行的說法 因此上面兩對條件是相同的 讀操做觀察到對變量v的最近一次寫 當多個goroutine同時訪問共享變量v時 它們須要利用同步事件來創建「發生在X以前」這個條件 來確保讀能夠觀察到寫

The initialization of variable v with the zero value for v's type behaves as a write in the memory model.

v變量用對應類型的零值初始化行爲 在內存模型中 和寫變量v是相似的

Reads and writes of values larger than a single machine word behave as multiple machine-word-sized operations in an unspecified order.

若是讀寫的值大於一個機器字 它們的行爲和 多機器字操做相似 次序不肯定

Synchronization
Initialization
Program initialization runs in a single goroutine, but that goroutine may create other goroutines, which run concurrently.

程序初始化 是在單一的goroutine環境進行的 可是 這個goroutine能夠再建立其它的goroutine 它們之間並行執行

If a package p imports package q, the completion of q's init functions happens before the start of any of p's.

若是包p導入了包q q的init函數在q的初始化以前執行

The start of the function main.main happens after all init functions have finished.

main.main函數在全部的初始化函數init結束後纔會執行

Goroutine creation
The go statement that starts a new goroutine happens before the goroutine's execution begins.

go語句會開啓一個新的goroutine 而且在這個goroutine執行前就建立好了

For example, in this program:

舉例來講:

var a string

func f() {
print(a)
}

func hello() {
a = "hello, world"
go f()
}
calling hello will print "hello, world" at some point in the future (perhaps after hello has returned).

在某個場合下調用hello函數 會打印「hello,world」 (多是在hello函數已經返回的狀況下)

Goroutine destruction
The exit of a goroutine is not guaranteed to happen before any event in the program. For example, in this program:

咱們肯定不了goroutine在退出的次序 好比在事件e以前它必須退出 舉例來講:

var a string

func hello() {
go func() { a = "hello" }()
print(a)
}
the assignment to a is not followed by any synchronization event, so it is not guaranteed to be observed by any other goroutine. In fact, an aggressive compiler might delete the entire go statement.

上面這段代碼 a的賦值操做並無跟在任何的同步事件以後 因此並不能保證它會被其它的goroutine觀察到 事實上 激進一點的編譯器會把這段代碼(go func(){a=fasd})刪掉

If the effects of a goroutine must be observed by another goroutine, use a synchronization mechanism such as a lock or channel communication to establish a relative ordering.

若是想在不一樣的goroutine之間觀察到變量的更新操做 那麼須要使用一些同步的機制 好比使用鎖 或者經過channel來通訊 從而創建起相對的讀寫次序

Channel communication
Channel communication is the main method of synchronization between goroutines. Each send on a particular channel is matched to a corresponding receive from that channel, usually in a different goroutine.

使用channel進行通訊 是在不一樣的goroutine間同步的主要方法 每一個對特定channel的send操做 一般都會對應於其它goroutine中的receive操做

A send on a channel happens before the corresponding receive from that channel completes.

channel的send操做 必定發生在對應的receive操做完成以前

This program: 下面這段代碼:

var c = make(chan int, 10)
var a string

func f() {
a = "hello, world"
c <- 0
}

func main() {
go f()
<-c
print(a)
}
is guaranteed to print "hello, world". The write to a happens before the send on c, which happens before the corresponding receive on c completes, which happens before the print.

能夠確保 打印「hello, world」 寫操做發生在channel c的send操做以前 send又發生在c的receive操做以前 receive發生在print以前

The closing of a channel happens before a receive that returns a zero value because the channel is closed.

關閉channel發生在接受以前 由於channel已經關閉了 mai中go f() 開啓了另外一個goroutine 在main中繼續執行的話 會執行到 <-c 而後在channel接收這阻塞 goroutine f() 開始執行 寫變量a 以後關閉channel 回到main 打印a channel在這裏純屬是用來同步的

In the previous example, replacing c <- 0 with close(c) yields a program with the same guaranteed behavior.

上面那段代碼中 若是用close(c)來替換c<-0 它的行爲是同樣的

A receive from an unbuffered channel happens before the send on that channel completes.

從無緩衝channel中接收操做 發生在該channel的發送操做結束前

This program (as above, but with the send and receive statements swapped and using an unbuffered channel):

下面這段代碼 和上面代碼同樣 可是接收和發送操做掉了個 而且使用無緩衝channel:

var c = make(chan int)
var a string

func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}
is also guaranteed to print "hello, world". The write to a happens before the receive on c, which happens before the corresponding send on c completes, which happens before the print.

這段代碼一樣能夠保證打印出「hello world」 對a的寫操做 發生在channel c的接收操做以前 接收操做發生在相應的發送操做完成前 而發送操做則發送在打印以前

If the channel were buffered (e.g., c = make(chan int, 1)) then the program would not be guaranteed to print "hello, world". (It might print the empty string, crash, or do something else.)

若是是有緩衝的channel 那麼上面那段代碼就沒法保證會打印「hello world」了

Locks
The sync package implements two lock data types, sync.Mutex and sync.RWMutex.

sync包實現了兩種類型的鎖 sync.Mutex和sync.RWMutex

For any sync.Mutex or sync.RWMutex variable l and n < m, call n of l.Unlock() happens before call m of l.Lock() returns.

對任意的sync.Mutex或者sync.RWMutex變量l n <m, 第N次調用l.Unlock 發生在 m次調用l.Lock返回前

This program:

var l sync.Mutex
var a string

func f() {
a = "hello, world"
l.Unlock()
}

func main() {
l.Lock()
go f()
l.Lock()
print(a)
}
is guaranteed to print "hello, world". The first call to l.Unlock() (in f) happens before the second call to l.Lock() (in main) returns, which happens before the print.

上述代碼能夠保證輸出「hello,world」 第一次調用l.Unlock 發生在第二次調用l.Lock以前 而其二次調用I.Lock發生在print操做以前

For any call to l.RLock on a sync.RWMutex variable l, there is an n such that the l.RLock happens (returns) after call n to l.Unlock and the matching l.RUnlock happens before call n+1 to l.Lock.

在sync.RWMutex變量l上的任何一次l.RLock操做 會存在一個n l.RLcok發生在調用第n次l.Unlock以後 對於的l.RUnlock發生在第n+1次l.Lock調用以前

Once
The sync package provides a safe mechanism for initialization in the presence of multiple goroutines through the use of the Once type. Multiple threads can execute once.Do(f) for a particular f, but only one will run f(), and the other calls block until f() has returned.

sync包爲在多goroutine環境下初始化提供了一種安全的機制 須要使用sync包的Once類型 多個線程能夠同時執行once.Do(f) 可是隻有一個線程會調用f() 其它的線程會阻塞 直到f()返回

A single call of f() from once.Do(f) happens (returns) before any call of once.Do(f) returns.

經過once.Do(f)調用f發生在任何其它的once.Do(f)返回以前

In this program:

var a string
var once sync.Once

func setup() {
a = "hello, world"
}

func doprint() {
once.Do(setup)
print(a)
}

func twoprint() {
go doprint()
go doprint()
}
calling twoprint causes "hello, world" to be printed twice. The first call to twoprint runs setup once.

調用兩次twoprint會輸出兩次「hello,world」

Incorrect synchronization
Note that a read r may observe the value written by a write w that happens concurrently with r. Even if this occurs, it does not imply that reads happening after r will observe writes that happened before w.

注意 讀操做r可能會觀察到和r並行的寫操做w 即便出現這樣的狀況 也不意味着r以後的讀會觀察到w以前的寫操做

In this program:

var a, b int

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

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

func main() {
go f()
g()
}
it can happen that g prints 2 and then 0.

上面這段代碼可能會輸出2 而後 輸出0

This fact invalidates a few common idioms. 這個事實讓不少習覺得常的事情做廢了

Double-checked locking is an attempt to avoid the overhead of synchronization. For example, the twoprint program might be incorrectly written as:

多檢查一次鎖 是避免同步問題的一種途徑 例如: twoprint程序可能被寫成下面這樣:

var a string
var done bool

func setup() {
a = "hello, world"
done = true
}

func doprint() {
if !done {
once.Do(setup)
}
print(a)
}

func twoprint() {
go doprint()
go doprint()
}
but there is no guarantee that, in doprint, observing the write to done implies observing the write to a. This version can (incorrectly) print an empty string instead of "hello, world".

可是 這樣寫並不能保證 doprint中觀察到對done的寫 就能觀察到對a的寫操做

Another incorrect idiom is busy waiting for a value, as in:

另外一種錯誤的用法是 忙等某一個變量值:

var a string
var done bool

func setup() {
a = "hello, world"
done = true
}

func main() {
go setup()
for !done {
}
print(a)
}
As before, there is no guarantee that, in main, observing the write to done implies observing the write to a, so this program could print an empty string too. Worse, there is no guarantee that the write to done will ever be observed by main, since there are no synchronization events between the two threads. The loop in main is not guaranteed to finish.

這樣寫的結果和上面的狀況同樣 這段代碼可能只會打印空字符串 更慘的是 並不能保證對done的寫 會在main中被觀察到 由於它們之間並無同步的事情 main中的loop並不能保證 能等到那個想要的值

There are subtler variants on this theme, such as this program.

也有一些上面代碼的變種:

type T struct {
msg string
}

var g *T

func setup() {
t := new(T)
t.msg = "hello, world"
g = t
}

func main() {
go setup()
for g == nil {
}
print(g.msg)
}
Even if main observes g != nil and exits its loop, there is no guarantee that it will observe the initialized value for g.msg.

效果同樣

In all these examples, the solution is the same: use explicit synchronization.

相關文章
相關標籤/搜索