Golang 内存模型

内存模型的目的是为了定义清楚变量的读写在不同执行体里的可见性

在 Golang 中,遵循的内存可见性原则主要包括如下几类:

关于初始化:

1.  如果 package p 引用了 package q,q 的 init() 方法 happens-before p

2.  main.main() 方法 happens-after 所有 package 的 init() 方法结束

关于 Goroutine:

3. 执行 goroutine 语句之前的变量赋值 happens-before 该 goroutine 的执行

package main

import (
	"log"
	"time"
)

var a, b, c int

func main() {
	a = 1
	b = 2
	go func() {
		c = a + 2
		log.Println(a, b, c)  // a、b 先于 go 语句,因此可见
	}()
	time.Sleep(1 * time.Second)
}

  我们可以确定 c=a+2 是happens-after a=1b=2,所以结果输出是可以确定的 1 2 3

 假如把 a=1 和 b=2 放到 go func() 语句后边,结果就是不可预期的了

4. goroutine 内的赋值不保证 happens-before 执行该 goroutine 语句后的语句

package main

import (
	"time"
	"fmt"
)

var a int

func main() {
	go func() {
		a = 1
	}()
	fmt.Println(a)  // a 可能是 0,也可能是 1
	time.Sleep(1 * time.Second)
}

因为 a=1 没有使用同步机制,并不能保证这个赋值被主 goroutine 可见

关于 Channel:

5. 对一个 Channel 的发送操作 happens-before 相应 Channel 的接收操作
6. 关闭一个 Channel happens-before 从该 Channel 接收到最后的返回值 0
7. 不带缓冲的 Channel 的接收操作 happens-before 相应 Channel 的发送操作
 

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,因为 a = "hello, world" happens-before c <- 0print(a) happens-after <-c, 根据 happens-before 的可传递性,a = "hello, world" happens-before print(a)。把 c<-0 替换成 close(c) 也能保证输出 hello,world,因为关闭操作在 <-c 接收到 0 之前发送。

package main

var c = make(chan int)
var a string

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

因为 channel 不带缓冲区,也就是说不能存储内容,所以 <-c 没准备好时 c<-0 会阻塞

因此执行 c<-0 时,f() 函数必然已经执行到 <-c,也就是说 a="hello,world" 必然已经执行完了,因此输出必然是 "hello,world"

关于锁:

8. 任何 sync.Mutex 或 sync.RWMutex 变量 l,定义 n < m, 第 n 次 l.Unlock() happens-before 第 m 次 l.lock() 调用

package main

import "sync"

var l sync.Mutex
var a string

func f() {
	a = "hello, world"
	l.Unlock()
}
func main() {
	l.Lock()
	go f()
	l.Lock()  // 先 unlock 才能二次 lock
	print(a)
}

  a="hello, world" happens-before l.Unlock() happens-before 第二个  l.Lock() happens-before print(a)

9. once.Do(f) 中的 f() happens-before 任何多个 once.Do(f) 调用的返回,且 f() 有且只有一次调用

import "sync"

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

 上面的代码虽然调用两次 doprint(),但实际上 setup 只会执行一次,

 并且并发的 once.Do(setup) 都会等待 setup 返回后再继续执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值