走进科学
发现问题
从一段问题代码开始
var (
lock sync.Mutex
instance *UserInfo
)
func getInstance() (*UserInfo, error) {
if instance == nil {
lock.Lock()
defer lock.Unlock()
if instance == nil {
instance = &UserInfo{
Name: "fan",
}
}
}
return instance, nil
}执行 go run -race go_race2.go 出现 WARNING: DATA RACE
什么是data race
Data races occur when multiple tasks or threads access a shared resource without sufficient protections, leading to undefined or unpredictable behavior.
在go里边,data race 意味着,多个goroutine访问(读/写)某个变量时,违反读写一致性。
问题定位
instance = &UserInfo{
Name: "fan",
}在这个位置 ,这一条命令,可以理解为三个指令
1. 创建UserInfo对象
2. 给该对象的Name属性赋值“fan”
3. 将该对象的地址传递给 instance
对于CPU的乱序指令操作,可能出现执行顺序为 1-3-2。
当 goroutine A 执行该函数时,若CPU执行指令刚好是1-3-2,在执行到3时,goroutine B 也执行该函数,此时判断 instance == nil时为false,就会返回一个 Name为空的UserInfo。
Memory Model
In computing, a memory model describes the interactions of threads through memory and their shared use of the data.
不同的语言,有不同的Memory Model,即 有不同的方式来处理多线程下的共享内存。主要解决了 CPU缓存一致性 和 CPU乱序执行 的问题。
CPU Cache
CPU缓存分为
- L1(单核独享)
- L1-I
- L1-D
- L2(单核独享)
L3(多核共享)
一致性协议
MESI协议中cache line有4个状态:
失效(
Invalid)共享(
Shared)独占(
Exclusive)已修改(
Modified)
假如有4个core,core0访问 x,它的cache line状态为 Exclusive,另外3个core也访问x,状态为 Shared。core0修改x之后,它的cache line状态为 Modified,其他的core状态改为 Invalid。
流水线与乱序执行
CPU性能=IPC(CPU每一时钟周期内所执行的指令多少)×频率(MHz时钟速度)
为了提高性能,可以增加多个核,增加单核的并行度。
流水线是为了加速了指令通过速度

仍会存在几个问题: - 指令长度不同,产生气泡 - 指令存在依赖关系 - 指令存在条件分支
所有又出现了 两个优化点: - 乱序执行 - 分支预测
对于有依赖情况的指令,仍需保证顺序执行,所以提供了 内存屏障(Memory barrier),避免 乱序执行的优化,影响到最终结果。 比如x86的内存屏障指令:
lfence (asm), void _mm_lfence (void) 读操作屏障
sfence (asm), void _mm_sfence (void)[1] 写操作屏障
mfence (asm), void _mm_mfence (void)[2] 读写操作屏障Go sync
Happens Before 是 Memory Model 中一个通用的概念。 要保证 W1 被 R2 读到,需满足: - W1发生在R2之前 - W1和R2之间的时间点,不能有其他写操作
golang实现 Happens Before 的地方: - init函数 - channel - sync - Mutex/RWMutex - sync - atomic - sync-once
CAS
compare and swap,用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。
ABA问题:地址X的值从 A 改为 B,又改回 A,这种情况CAS会失效。