golang内存模型

栏目: IT技术 · 发布时间: 5年前

内容简介:多个goroute同时修改一个数据必须是有序的使用channel或sync、sync/atomic包中提供的同步原语,可保证对数据顺序访问Initialization
  • 介绍
go内存模型是指在特定的条件下,向goroutine中的变量写入值,在另一个goroutine中能够读取到该变量的值
  • 建议

多个goroute同时修改一个数据必须是有序的

使用channel或sync、sync/atomic包中提供的同步原语,可保证对数据顺序访问

  • happens before

单个goroutine读写必需按一定顺序执行,编译器和处理器只有在不改变程序执行最终结果的情况下会对单个goroutine的读写重新排序。重新 排序 会导致一个goroutine看到的行为与另一个goroutine不一致。比如在一个goroutine中执行a=1;b=2,另外的goroutine看到的b值的更新发生在a值更新之前。

golang的内存操作读写请求的happens before:事件e1 happens before 事件e2,则e2在e1后执行。事件e1不是happen before 事件e2且不是在事件e2之后执行,则说事件e1和事件e2是同时执行的。

单个goroutine的happens-before是由程序顺序表达。

以下两条件都满足时一个写操作w向变量v写入数据,读操作r可以观察到变量v的值

1、读操作r没有 happen before 写操作w

2、在写w操作之后与读r操作之前,没有其他的写操作w’’对变量v写入数据

为了保证读操作r读到变量v,是写操作w向变量v写入的值,必须符合以下两个条件

1、写操作w happens before 读操作r

2、其他的写变量V的操作要么发生在写操作w之前,要么发生在读操作r之后

这部分限制条件比第一种限制条件严格,要求没有其他写操作同时与写操作w或读操作r一起执行。

单个goroutine没有并发问题,写操作w的变量v可以被读操作r读取到。当多个goroutines访问共享变量v,必须使用同步事件建立执行顺序,确保写操作的变量值被读操作正确读取。

在内存模型中将初始化变量v类型零值的行为作为一次写操作。

读写值大于32位(4 bytes)或64位操作系统(8 bytes)时的操作行为,跟在多个32位或64位操作系统操作的行为顺序一致是不明确的。

  • 同步

Initialization

程序初始化运行单个goroutine,但这个goroutine可能创建其他同时运行的goroutines。

package p导入package q,q的init方法比p的先执行。

main方法在所有init方法完成初始化后执行

goroutine creation

go声明启动一个goroutine happens before 执行goroutine,示例如下:

var a string

func f() {
        print(a)
}

func hello() {
        a = "hello, world"
        go f()
}

未来可能执行hello方法可能会打印出”hello, world”,目前不会

goroutine destruction

一个goroutine的退出不保证任何事件的执行顺序,示例

var a string

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

给变量a赋值不是一个同步事件,所以不能保证变量a可以被其他goroutine获取到。这段a的赋值操作代码可能在程序编译阶段直接被丢弃掉了。

假如一个goroutine变量值对另一个goroutine可获取到,需要使用锁或信道(channel)通信同步机制保证执行顺序。

Channel communication

goroutines之间的消息同步的主要是通过信道通信(channel)方式实现。在不同的groutine中,每个发送的信道,有个对应的接收信道与其对应。

发送信道happens before接收信道。示例:

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”,写入变量a happens before发送信息至信道c,发送信道happens before从接收信道c中获取信息,接收信息happens before打印动作print.

一个关闭的信道happens before 接收信道,未向信道中发送信息,会返回一个零值,因为信道已经关闭了。可通过替换代码中c<- 0为close(c)代码输出结果是一致的。

下面这段代码类似,使用无缓存的信道通信且变换下发送与接收语句

var c = make(chan int)
var a string

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

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

由于是无缓存信道通信,所以同样会保证输出”hello, world”。写变量a happens before从接收信道c获取数据,从接收信道获取数据happens before从信道c中发送数据,从信道c中发送数据happens before 打印动作Print

假如使用的是缓存信道(如c = make(chan int, 1)),这段代码不能保证输出“hello, world”。代码可能输出空的字符串,崩溃,或其他。

向一个容量为C的信道发送数据,从该信道接收数据时第k个数据接收happens before 第k+c个数据接收

总结缓存信道通信规则。缓存信道通信方式允许统计信号量:信号量数为活跃使用数,信号量容量为最大活跃使用数,发送数据请求信号量,接收数据释放信号量。这是用于限制并发的常用方法。

下列代码,为work启动一个goroutine,使用了信道限制确保同一时刻最多有三个work在运行。

var limit = make(chan int, 3)

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

Locks

sync包实现了sync.Mutex 和sync.RWMutex两种数据类型的锁。

变量l为sync.Mutex or sync.RWMutex锁,假如n<m, n调用l.Unlock()返回happens before m调用l.Lock() 返回

示例:

var l sync.Mutex
var a string

func f() {
        a = "hello, world"
        l.Unlock()
}

func main() {
        l.Lock()
        go f()
        l.Lock()
        print(a)
}

代码保证输出“hello, world”。在f方法中第一次调用l.Unlock()返回happens before在main方法中第二次调用l.Lock()返回,可正常打印输出变量a的值

变量l为 sync.RWMutex调用 l.RLock时,n调用l.RLock 返回happens after n调用l.Unlock,同时l.RUnlock happens before n+1调用l.Lock

Once

在多个groroutine中sync包使用Once类型安全初始化,多个线程同时执行once.Do(f),f()只会执行一次,其他访问请求阻塞至f()执行完后返回。

从once.Do(f)的一个访问f()返回happens before任何其他从 once.Do(f)访问返回

示例:

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

两次执行twoprint方法只会执行setup方法一次。setup方法在所有print方法前执行完成。结果是打印两次“hello, world”

Incorrect synchronization

注意,读操作r可能观察到与读操作r同时发生的写w操作变量值,这并不意味着读操作执行happening after读操作r可以观察到写happened before写操作w的值

示例:

var a, b int

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

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

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

代码可能发生执行方法g输出2和0,但不能保证的。

使用双重检测锁避免同步开销,如下twoprint代码片断错误写法示例如下:

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。这个版本的是错误的,可能会输出一个空的字符串,而不是“hello, world”。

另外一种错误是等待一个值,类似:

var a string
var done bool

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

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

跟前面的例子一样,这段代码也有可能输出空字符串,观察写数据至变量done,意味着观察写数据至变量a。在两个线程中,没有同步事件用于保证main方法可以观察到写入done变量的值,不能保证main方法正确执行。

类似变种代码片断示范,如下:

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

即使main方法观察到g != nil然后退出循环,还是不能保证main方法可以观察到g.msg的初始化值。

所有的这些错误的例子,表明要从一个gorotine观察到另一个goroutine赋值问题必须使用显式的同步机制。

来源: https://golang.org/ref/mem

关联的部分面试题目

该程序片段输出内容是什么?这种问法是否有误,是否换应该换种思路问:如要输出正确的i值应该怎么处理?

func main() {
    runtime.GOMAXPROCS(1)
    wg := sync.WaitGroup{}
    wg.Add(10)
    for i := 0; i < 10; i++ {
        go func() {
            fmt.Println("A: ", i)
            wg.Done()
        }()
    }
    wg.Wait()
}

该程序片断使用是否有问题?如有如何纠正?

func goRoutineA(a <- chan int) {
    val := <- a
    fmt.Println("goRoutineA received the data", val)
}

func goRoutineB(b <- chan int) {
    val := <- b
    fmt.Println("goRoutineB received the data", val)
}

func main() {
    ch := make(chan int)
    go goRoutineA(ch)
    go goRoutineB(ch)
    ch <- 3
    time.Sleep(time.Second * 1)

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

About Face 3

About Face 3

Alan Cooper、Robert Reimann、David Cronin / John Wiley & Sons / 2007-5-15 / GBP 28.99

* The return of the authoritative bestseller includes all new content relevant to the popularization of how About Face maintains its relevance to new Web technologies such as AJAX and mobile platforms......一起来看看 《About Face 3》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具