Go——内存模型

懒无趣 · · 644 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

什么是内存模型

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.

  1. 如果包p import 了包q,那p包运行必须在q包init函数执行完之后(q包init函数 happens before p包全部运行)
  2. 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)
}
  1. 第 n 次的 m.Unlock 一定 happens before 第 n+1 m.Lock 方法的返回;
  2. 对于读写锁 RWMutex m,如果它的第 n 个 m.Lock 方法的调用已返回,那么它的第 n 个 m.Unlock 的方法调用一定 happens before 任何一个 m.RLock 方法调用的返回,只要这些 m.RLock 方法调用 happens after 第 n 次 m.Lock 的调用的返回。这就可以保证,只有释放了持有的写锁,那些等待的读请求才能请求到读锁。
  3. 对于读写锁 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

Go内存模型


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:懒无趣

查看原文:Go——内存模型

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

644 次点击  
加入收藏 微博
上一篇:go语言面试题
下一篇:聊聊klog的Flush
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传