go的内存模型

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

Introduction

Go内存模型说明在什么情况下,可以保证一个goroutine中某变量a的读取,可以是另外一个goroutine对变量a写入的值。

Advice

修改由多个goroutine同时访问的数据的程序必须序列化此类访问。要序列化访问,请使用channel或其他同步原语(如sync和sync/atomic包中的同步原语)保护数据。

Happens Before

在一个goroutine中,读写操作必须按照程序指定的顺序执行。也就是说,只有在重新排序不会改变语言规范定义的goroutine内的行为时,编译器和处理器才可以重新排序在单个goroutine内执行的读写操作。由于这种重新排序,一个goroutine观察到的执行顺序可能与另一个goroutine观察到的顺序不同。例如,如果一个goroutine执行a=1;b=2;,另一个goroutine可能会在a的更新值之前观察b的更新值。

为了指定读写的需求,我们定义了happens before(Go程序中内存操作执行的部分顺序)。如果事件e1发生在事件e2之前,那么我们说e2发生在事件e1之后。另外,如果e1不发生在e2之前,也不发生在e2之后,那么我们说e1和e2同时发生。

在单个goroutine中,happers before顺序就是语句表示的顺序。

V = W

...

R = V

在什么情况下R读到的值就是W呢?

情况一

如果同时满足下面的条件,则可能读到:

  1. 动作读r没有happen before 动作写w
  2. 没有其他的写w1 happen after w 但是 before r

情况二

如果同时满足下面的条件,则保证读到:

  1. 动作写 w happen before 动作读 r
  2. 其他动作写 happens before w or after r

Synchronization

Initialization

程序初始化在单个goroutine中运行,但该goroutine可能会创建其他goroutine,这些goroutine同时运行。

  1. 如果包p导入包q,q的init函数 happen before p
  2. 所有的init函数happen before main.main函数

Goroutine creation

从某函数启动一个goroutine,happen before该goroutine执行。

package main

import "time"

var a string

func f() {
    print(a)
}

func hello() {
    go f()
    a = "hello, world" // 放在go前面,输出也是一样的,hello world
}

func main() {
    hello()
    <-time.After(1)
}

https://play.golang.org/p/th21kxXhRvU

Goroutine destruction

在某个函数里面,某个goroutine的结束不能保证happen before该函数里面的动作。

package main

import "time"

var a string

func hello() {
    go func() { a = "hello" }()
    print(a)
}

func main() {
    hello()
    <-time.After(1)
}

修正该问题,需要加上channel或者锁

Channel communication

buffered channel

  1. A send on a channel happens before the corresponding receive from that channel completes.
  2. close channel happend before chanel receive
package main

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

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

func main() {
    go f()
    <-c
    print(a)
}

输出 hello, world
https://play.golang.org/p/qbn76DYCceg

unbuffered channel

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

package main

var c = make(chan int)
var a string

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

输出 hello,world
https://play.golang.org/p/M15nzoy6Wlk
但是,上面的代码中,如果是对bufferd channel,输出的结果不可预知。

The kth receive on a channel with capacity C happens before the k+Cth send from that channel completes.

var limit = make(chan int, 3)

func main() {
    for _, w := range work {
        go func(w func()) {
            limit <- 1
            w()
            <-limit
        }(w)
    }
    select{}
}

Locks

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)
}

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()
}

打印2次

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.

package main

var a, b int

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

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

func main() {
    go f()
    g()
}

输出: 00
https://play.golang.org/p/cPcl12-uJFg

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()
}

在doprint中,观察到done的值发生改变,不一定观察到a的值发生改变


var a string
var done bool

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

func main() {
    go setup()
    for !done {
    }
    print(a)
}

和上面一个例子是一样的。还有一个问题就是,这个done可能一直为false


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)
}

即使退出循环,但是也不能保证打印出初始化后g.msg的值。


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

本文来自:简书

感谢作者:

查看原文:go的内存模型

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

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