- 前言
- 一、什么是go的内存模型?
- 二、为什么需要这些条件?
- 三、Happen Before
- init函数
- 创建/销毁Goroutine
- channel
- 锁
- sync.Once
- 总结
前言
golang内存模型是什么:
golang面试经常被问到内存模型,不知道怎回答。不知道面试官考察什么。今天就带你来了解golang内存模型
一、什么是go的内存模型?
二、为什么需要这些条件?指定了一系列条件,在这些条件下,可以保证在协程中对变量的读取操作可以观察到其他协程对同一变量写操作的结果,这就是go内存模型。
因为编译器无法保证指令执行顺序与程序书写顺序一致。
如下示例:
package main
func main() {
i := 5
go func() {
i = 10
}()
for j := 0;j<100;j++ {
fmt.Println(i)
}
}
多运行几次,会发现结果可能不同,但可以发现,部分Println打印的i变量依然是5,说明未能立观察到协程对i变量的写操作。
所以我们为了保证协程间的变量读取的可观察,就需要建立Happen Before关系的同步事件。也就是内存模型设定的条件。
三、Happen Before以下几种方式,可以建立Happen Before关系的同步事件:
init函数1.init函数
2.创建/销毁goroutine
3.channel
4.锁
5.Once
包a引入包b,那么包b的init就会happen before 包a的init函数。如下:
// a.go
package a
import "b"
func init() {
fmt.Println("a")
b.DoSomething()
}
// b.go
package b
func init() {
fmt.Println("b")
}
func DoSomething() {
fmt.Println("test")
}
输出如下:
b a test创建/销毁Goroutine
创建goroutine happen before goroutine执行
goroutine执行 happen before goroutine销毁
对于无缓冲的channel,recv操作happen before send
package main
import "fmt"
import "time"
func main() {
i := make(chan int)
go func() {
fmt.Println("send")
i <- 10
}()
go func() {
fmt.Println("recv")
fmt.Println(<-i)
}()
time.Sleep(time.Second)
}
输出结果:
send recv 10
关闭channel的操作 happen before 接受到0值。
对于容量为m的channel,第n次recv是happen before 第n+m次send
package main
import "fmt"
import "time"
func main() {
ch := make(chan int, 5)
go func() {
for i:=0;i<10;i++ {
time.Sleep(100)
fmt.Println("send", i)
ch <- i
}
close(ch)
}()
go func() {
for i := range ch {
time.Sleep(100)
fmt.Println("receive", i)
}
}()
time.Sleep(time.Second * 2)
}
输出结果发现,对于长度为5的channel,第1次接收比6次发送要先执行。
锁对于任意的sync.Mutex或者sync.RWMutex,n
对于fn()的单个调用会happen before所有的once.Do(fn)返回之前发生
package main
import "fmt"
import "sync"
func main() {
var o sync.Once
i := 5
for j := 0;j<3;j++ {
o.Do(func() {
i += 1
})
fmt.Println(i)
}
}
执行结果:
6 6 6总结
虽然这些都是go开发中一般都知道的常识,但是我们还是需要了解,为什么会这样,为什么需要这样?
在面试过程中问go的内存模型,就只要抓住happen before来回答就好



