锁获取后是否需要内存屏障?基于多CPU无缓存系统的技术疑问
自旋锁内存屏障的正确性分析
系统与代码背景
- 硬件环境:3颗共享内存和总线的CPU,无缓存但配备存储缓冲区
- 指令支持:原子CAS指令;三种内存屏障:
LoadMemoryBarrier():阻止加载操作跨屏障重排StoreMemoryBarrier():阻止存储操作跨屏障重排GeneralMemoryBarrier():阻止所有内存访问跨屏障重排
- CPU重排规则:可任意重排存储与存储/加载、加载与存储/加载操作,以及这些操作与原子操作的顺序
- 语言标准:C99,无法使用stdatomic库,CAS无隐式内存屏障
示例代码
unsigned int spinlock = 0; int x = 0; void lock() { while (CAS(&spinlock, 1, 0) == 1); // Wait to acquire lock // Spinlock acquired LoadMemoryBarrier(); } void unlock() { GeneralMemoryBarrier(); spinlock = 0; } void CPU1() { lock(); x += 1; unlock(); } void CPU2() { lock(); x -= 1; unlock(); } void CPU3() { printf("%d", x); }
用户疑问
对x的存储操作是否可能被重排到自旋锁获取之前?认为不会,因此lock()函数内只需LoadMemoryBarrier()即可,该理解是否正确?能否给出该屏障不足以保证的场景?
回答
你的理解不正确,对x的存储操作确实可能被重排到自旋锁获取之前,LoadMemoryBarrier()无法满足自旋锁的内存语义要求,具体问题场景如下:
1. 重排风险的核心原因
LoadMemoryBarrier()仅对加载操作的重排有约束,完全不限制存储操作的重排行为。而CPU允许存储操作(比如x +=1对应的最终存储)与原子CAS指令之间的重排,因此临界区内的存储操作完全可能被CPU重排到CAS成功获取锁之前执行。
2. 具体错误执行场景
假设CPU1、CPU2、CPU3按以下顺序执行:
- CPU1发起CAS尝试获取锁,此时
spinlock为0,CAS执行成功(将spinlock设为1),但CPU的存储缓冲区将x +=1的存储操作重排到CAS之前执行,直接把x的值从0修改为1。 - 此时CPU3执行
printf("%d", x),读取到x=1。 - 随后CPU2开始执行lock(),因
spinlock为1进入循环等待;CPU1执行unlock()中的GeneralMemoryBarrier()和spinlock=0操作,释放锁。 - CPU2检测到
spinlock为0,CAS获取锁后执行x -=1,将x修改为0。
这个场景中,CPU1对x的修改是在未持有锁的状态下完成的,完全违背了自旋锁的互斥语义——自旋锁的核心作用是保证临界区操作必须在持有锁期间执行,而当前的LoadMemoryBarrier()无法阻止这种破坏互斥性的重排。
3. 正确的lock()实现
在CAS成功获取锁后,需要插入StoreMemoryBarrier()或GeneralMemoryBarrier(),而非LoadMemoryBarrier()。StoreMemoryBarrier()可以确保屏障之后的所有存储操作(临界区内对x的修改)不会被重排到屏障之前的CAS操作之前,从而保证临界区操作严格在锁持有期间执行。
内容的提问来源于stack exchange,提问作者user1806687
相关产品推荐
相关产品推荐

