077-内存同步
Posted --Allen--
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了077-内存同步相关的知识,希望对你有一定的参考价值。
还记得前面的银行账户存款的例子吗?为了解决竞争问题,我们曾经使用了 sync.Mutex 来保护共享变量。
特别的,在读取 balance 变量的时候,我们是这样写的:
func Balance() int
mu.Lock()
defer mu.Unlock()
return balance
当时我们说到,这里加锁需要进一步讨论。这种情况也是并发编程里让人感到相当困惑的地方。
1. 例子
我们来看一个简单的例子。
var x, y int
// goroutine A
go func()
x = 1
fmt.Printf("y: %d ", y)
()
// goroutine B
go func()
y = 1
fmt.Printf("x: %d ", x)
()
仅通过分析,我们能预见的可能的结果有:
y: 1 x: 1
x: 0 y: 1
y: 1 x: 0
x: 1 y: 1
然而,实际在运行的时候,可能还会出现下面的结果:
x: 0 y: 0
y: 0 x: 0
这已经不是传统意义上的编译器和 cpu 的工作方式了。这个问题不只会出现在 Golang 里,同样也会出现在你所编写的 C/C++ 的程序里。(并发编程是一件挑战性极高的事,不是说加个锁你的程序就没问题了。)
上面的结果要怎么解释呢?
1.1 指令重排
一般来说,在一个 goroutine 里,每条语句的执行顺序都是保证的。但是,在不使用 channel 且不使用 mutex 这样的显式同步操作的时候,我们就没办法保证每条语句的执行顺序(指令重排)。
这样一来,编译器就有可能在编译这两个 goroutine 的时候,就有可能将赋值操作放到 print 语句后面。
1.2 内存同步
除了编译器指令重排可能导致这种问题外,在多核处理器中,还会有主存同步的问题。
现代处理器为了提高运行效率,每个核心都有自己的 Local Cache,并且在必要的时候,将其 commit 到内存(主存同步)。
在 Golang 里像 channel 通信,互斥操作这样的原语都会触发“主存同步”。只有这样,在一个 goroutine 里的执行结果,才能被运行在其它 cpu 或其它核心的 goroutine 所看到。
在上面的盒子里,goroutine A 和 B 都没有使用 channel 或是 mutex 这样的操作,因此有可能不会触发主存同步。如果这两个 gorotuine 运行在不同的 cpu 核心上,这样一来,两个 goroutine 所看到的内存也可能不一样(内存可见性)。
假设 goroutine B 先执行,y = 1, 并打印出 x = 0,但是 goroutine A 所看到的 y 未必就是 1. 因为 goroutine A 运行在自己的核心上,它所读取的是自己核心中的缓存,所以 goroutine 可能看到的 y 还是 0。
2. 总结
- 主存同步
- 内存一致性
- 指令重排
以上是关于077-内存同步的主要内容,如果未能解决你的问题,请参考以下文章