什么是内存模型
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.
内存模型指导的是不同goroutine对内存中同一变量进行读写操作对同步性。
Happens Before
Within a single goroutine, the happens-before order is the order expressed by the program.
在单一goroutine的情况下,happends-before 的顺序就是程序锁表达的顺序
A read r of a variable v is allowed to observe a write w to v if both of the following hold:
r does not happen before w.
There is no other write w' to v that happens after w but before 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:
w happens before r.
Any other write to the shared variable v either happens before w or after 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.
如果要保证对“变量 v 的读操作 r”能够观察到一个对“变量 v 的写操作 w”,并且 r 只能观察到 w 对变量 v 的写,没有其它对 v 的写操作,也就是说,我们要保证 r 绝对能观察到 w 操作的结果,那么就需要同时满足两个条件:w happens before r;其它对 v 的写操作(w2、w3、w4, …) 要么 happens before w,要么 happens after 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,必须使用同步原语(synchronization events)来保证读写顺序。
The initialization of variable v with the zero value for v's type behaves as a write in the memory model.
变量v的零值初始化可以看作是一次写操作
Reads and writes of values larger than a single machine word behave as multiple machine-word-sized operations in an unspecified order.
读写一个大于一个机器字节(machine word,64位系统位64bit,32位则位32bit)的操作可以看作是随机的(即不可保证顺序的正确性)
Synchronization
Init函数
Initialization
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.
- 如果包p import 了包q,那p包运行必须在q包init函数执行完之后(q包init函数 happens before p包全部运行)
- main.main 函数 必须在 init函数完成之后执行 (init happens before main)
Goroutine creation
The go statement that starts a new goroutine happens before the goroutine's execution begins.
var a string
func f() {
print(a)
}
func hello() {
a = "hello, world"
go f()
}
go语句 happens before go出去的goroutine代码执行,即上述代码能保证a先赋值(a= "hello, world"),然后在读a的值(print(a))。
Goroutine destruction
The exit of a goroutine is not guaranteed to happen before any event in the program.
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
var a string
func hello() {
go func() { a = "hello" }()
print(a)
}
goroutine的退出是不能被保证happen before的,上述的代码是不能保证print(a)可以读到"hello"的值
这个时候就需要用到其他同步原语(synchronization mechanism)来确保你想要的顺序。
Channel communication
buffered channel
A send on a channel happens before the corresponding receive from that channel completes.
var c = make(chan int, 10)
var a string
func f() {
a = "hello, world"
c <- 0
}
func main() {
go f()
<-c
print(a)
}
在一个有缓冲区的channel上,同一次序(比如手都是第2个)的发送操作 happen before 接受操作。即当channel没有数据时,<-chan 会阻塞,只有当有数据发送完成时,接受操作才会继续往下执行
The closing of a channel happens before a receive that returns a zero value because the channel is closed
即上述的代码中将 c <- 0
替换为close(c)也能达到同样的效果。
unbuffered channel
A receive from an unbuffered channel happens before the send on that channel completes.
var c = make(chan int)
var a string
func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}
在一个无缓冲区的channel上,接受操作是 happen before 发送操作的,即发送操作会一直阻塞等待有其他goroutine来接受并完成时才会继续执行。
Lock
The sync package implements two lock data types, sync.Mutex and 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.
var l sync.Mutex
var a string
func f() {
a = "hello, world"
l.Unlock()
}
func main() {
l.Lock()
go f()
l.Lock()
print(a)
}
- 第 n 次的 m.Unlock 一定 happens before 第 n+1 m.Lock 方法的返回;
- 对于读写锁 RWMutex m,如果它的第 n 个 m.Lock 方法的调用已返回,那么它的第 n 个 m.Unlock 的方法调用一定 happens before 任何一个 m.RLock 方法调用的返回,只要这些 m.RLock 方法调用 happens after 第 n 次 m.Lock 的调用的返回。这就可以保证,只有释放了持有的写锁,那些等待的读请求才能请求到读锁。
- 对于读写锁 RWMutex m,如果它的第 n 个 m.RLock 方法的调用已返回,那么它的第 k (k<=n)个成功的 m.RUnlock 方法的返回一定 happens before 任意的 m.RUnlockLock 方法调用,只要这些 m.Lock 方法调用 happens after 第 n 次 m.RLock。
Once
A single call of f() from once.Do(f) happens (returns) before any call of once.Do(f) returns.
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()
}
once.Do(func) 是 happen after func函数执行完成的,即doprint函数中必然能保证print(a)这行执行的时候,setup函数已经执行完成。
Reference
有疑问加站长微信联系(非本文作者)