x86-64汇编实现自旋锁仅首个线程进入临界区问题咨询
问题原因
你的自旋锁实现存在两个核心错误,该现象和多核心下的缓存一致性机制直接相关:
- 锁获取操作不具备原子性
你使用shrb $1,.L0C_A1(%rip)作为锁的抢锁逻辑,这是典型的读-改-写(RMW)操作。x86架构下,普通RMW指令默认不会在多核心环境下保证原子性,多个线程可以同时读取到锁值为1的状态,分别移位后同时写回0,最终锁值变为0,但多个线程都认为自己抢到了锁,会引发临界区并发问题。 - 自旋阶段存在写风暴
你将带写操作的shrb直接放在自旋循环中,所有等待锁的线程会不断对锁所在的内存地址执行写操作。每个写操作都需要先获取对应缓存行的独占所有权,大量并发的RFO(所有权请求)会导致锁所在的缓存行在多个核心之间反复弹跳,不仅性能极低,还会导致持有锁的核心释放锁的写操作被长时间阻塞。甚至会出现锁刚被释放为1,就立刻被等待线程的shrb改写为0,由于原子性缺失,等待线程无法正确捕获到这次锁空闲的状态,最终表现为只有第一个线程能进入临界区。
为什么GDB断点后可以正常运行
当你在释放锁的指令处设置断点时,GDB会暂停所有线程的执行,你单步执行完movb $1,.L0C_A1后,锁已经被成功设置为1,此时再恢复所有线程运行,没有了持续的写操作争抢缓存行,线程可以正常读取到锁的空闲状态,因此可以正常抢锁进入临界区。
修复方案
1. 给RMW操作加lock前缀保证原子性
lock前缀会锁总线/锁缓存行,保证读改写操作的原子性,修改抢锁指令为:
lock shrb $1,.L0C_A1(%rip)
2. 优化自旋逻辑,避免自旋阶段写操作
先通过只读判断检测锁是否空闲,只有锁空闲时再执行原子抢锁操作,大幅减少缓存行争抢:
func: spin_wait: cmpb $1, .L0C_A1(%rip) # 只读判断锁是否空闲 jne spin_wait # 锁被占用就继续自旋 lock shrb $1,.L0C_A1(%rip) # 锁空闲时才尝试原子抢锁 jnc spin_wait # 抢锁失败回到自旋阶段 # 原有临界区代码不变 movq x(%rip),%rax xorl %esi,%esi pushq $10000000 pushq %rsi movq %rsp,%rdi pushq %rax leal 35(%rsi),%eax syscall # sleep 10 ms pop %rax addq $16,%rsp xorl %edx,%edx mulq %rax # x = x * x movq %rax,x(%rip) # store new x movb $1,.L0C_A1(%rip) # leave critical section xorl %eax,%eax ret
内容的提问来源于stack exchange,提问作者pajacol
相关产品推荐
相关产品推荐

