The Go Memory Model
memory model 其实各大语言似乎都有,所以看到下面的代码你大概不会陌生
1 | var a string |
相信你可以用经验判断上面的例子哪里有问题,实际上,一个程序面临着:
- 编译器可能把无关的代码乱序以提高效率
- CPU 可能会产生执行上的乱序(比如 Intel 的 Store-Load 乱序)
- 多核内存上有各种各样的麻烦
- 虽说内存和并发关系暧昧不清,但是毫无疑问,前者对写代码是存在影响的。有的时候甚至会给人带来奇怪的印象和吊诡的设计,比如 C 和 Java 不同含义的 volatile.
那么 Go 同样要面临这样的问题,下面的代码能够很好的运作。
1 | var wg sync.WaitGroup |
还有:
1 | xChan = make(chan X) |
为什么呢?我们来看看这个 blog 吧:https://golang.org/ref/mem
Happens Before
Within a single goroutine, the happens-before order is the order expressed by the program.
A read r of a variable
v
is allowed to observe a write w tov
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 tov
, 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.
原文还是比较精炼的,我就直接翻译了。目前的 happens before 针对的对象是单个变量。注意
Reads and writes of values larger than a single machine word behave as multiple machine-word-sized operations in an unspecified order.
关于 happens before, Go 语言的介绍还是很简单的,我倾向于阅读一下 C++ Concurrency In Action, 摘录下面关于 happens-before 的一段
At the basic level, inter-thread happens-before is relatively simple and relies on the synchronizes-with relationship introduced in section 5.3.1: if operation A in one thread synchronizes-with operation B in another thread, then A inter-thread happens- before B. It’s also a transitive relation: if A inter-thread happens-before B and B inter- thread happens-before C, then A inter-thread happens-before C. You saw this in listing 5.2 as well.
Inter-thread happens-before also combines with the sequenced-before relation: if operation A is sequenced before operation B, and operation B inter-thread happens- before operation C, then A inter-thread happens-before C. Similarly, if A synchronizes- with B and B is sequenced before C, then A inter-thread happens-before C. These two together mean that if you make a series of changes to data in a single thread, you need only one synchronizes-with relationship for the data to be visible to subsequent opera- tions on the thread that executed C.
以上你就有了关于 happens before 的基本认知。
黑暗面
下面内容是我胡扯的
This hardware must obey the following ordering constraints [McK05a, McK05b]:
- Each CPU will always perceive its own memory accesses as occurring in program order.
- CPUs will reorder a given operation with a store only if the two operations are referencing different locations.
- All of a given CPU’s loads preceding a read memory barrier (smp_rmb()) will be perceived by all CPUs to precede any loads following that read memory barrier.
- All of a given CPU’s stores preceding a write mem- ory barrier (smp_wmb()) will be perceived by all CPUs to precede any stores following that write memory barrier.
- All of a given CPU’s accesses (loads and stores) preceding a full memory barrier (smp_mb()) will be perceived by all CPUs to precede any accesses following that memory barrier.
Synchronization
Initialization
Program initialization runs in a single goroutine, but that goroutine may create other goroutines, which run concurrently.
If a package
p
imports packageq
, the completion ofq
‘sinit
functions happens before the start of any ofp
‘s.The start of the function
main.main
happens after allinit
functions have finished.
就是 init
阶段形成一棵树或者DAG,后被 import 的先被初始化。
Goroutine creation
The
go
statement that starts a new goroutine happens before the goroutine’s execution begins.
官方文档的例子很好,一字不易
1 | var a string |
go
before goroutine’s begina
会先被初始化
Goroutine destruction
The exit of a goroutine is not guaranteed to happen before any event in the program. For example, in this program:
1
2
3
4
5
6 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 entirego
statement.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.
(其实我很好奇,这一段能不能过编译)
Channel communication
A send on a channel happens before the corresponding receive from that channel completes.
A receive from an unbuffered channel happens before the send on that channel completes.
回想起开头的代码,不难理解为什么下列代码是合理的
1 | xChan = make(chan X) |
代码还有个比较有意思的地方是限流
1 | var limit = make(chan int, 3) |
sync 库
A single call of
f()
fromonce.Do(f)
happens (returns) before any call ofonce.Do(f)
returns.For any call to
l.RLock
on async.RWMutex
variablel
, there is an n such that thel.RLock
happens (returns) after call n tol.Unlock
and the matchingl.RUnlock
happens before call n**+1 tol.Lock
.
如果 Lock
Once
有问题,那我们的程序真应该出毛病了!实际上,程序最早的 wg.Done
也有 happens-before 语义!