美文网首页
Go——内存模型

Go——内存模型

作者: 懒无趣 | 来源:发表于2020-12-27 22:25 被阅读0次

    什么是内存模型

    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——内存模型

          本文链接:https://www.haomeiwen.com/subject/xlkanktx.html