多线程执行lock cmpxchg时,是否可能全部CAS操作失败?
lock cmpxchg是否可能全部失败? 首先直接给出结论:不可能出现两个线程的lock cmpxchg全部失败的情况,原因和lock前缀的原子性保证直接相关,我们来一步步拆解:
1. lock cmpxchg的核心原子性特性
lock前缀会将后续指令的整个操作序列(读取目标内存值 → 和eax比较 → 根据结果写入新值/原值)转化为一个原子总线周期。这意味着,在一个线程执行lock cmpxchg的全过程中,其他线程的lock类内存操作无法介入该目标地址的总线访问——处理器会通过总线锁定或缓存一致性协议(如MESI)确保整个操作的原子性,不会出现“半完成”的状态。
2. 针对你的场景分析
你给出的场景中,两个线程的eax都持有目标内存的初始旧值0x62ab1,目标地址[ebx]的初始值也是0x62ab1:
- 假设线程1先抢到总线执行
lock cmpxchg:它会原子性地读取[ebx]的旧值,和eax匹配,成功将自己的源值(你的代码里写的是eax,这里应该是笔误,实际应该是另一个寄存器,不过不影响核心逻辑)写入[ebx]。整个操作完成后,[ebx]的值已经更新。 - 当线程2再执行
lock cmpxchg时,它会原子性地读取到已经被线程1修改后的新值,和自己eax里的旧值不匹配,因此比较失败,将当前[ebx]的新值写回(等于未改变内存状态),操作失败。
反过来,如果线程2先抢到总线,那么线程2的操作成功,线程1的操作失败。无论哪种情况,必然有且仅有一个线程的lock cmpxchg操作成功,另一个失败,不可能两者都失败。
3. 关于你猜想的澄清
你提到“两个线程都可能执行写周期,之后因读取到过时值导致比较失败”,这个猜想的问题在于忽略了lock前缀的原子性保障:每个lock cmpxchg的读取和写回是绑定在一起的原子操作,不会出现“线程A读取旧值,线程B也读取旧值,然后两者都写回”的情况——第一个线程的写回会在第二个线程的读取之前完成,第二个线程读取到的已经是更新后的值,所以只会有一个线程读取到旧值并成功,另一个读取新值失败。
另外补充你引用的指令说明细节:
无论比较结果如何,目标操作数都会经历一个写周期。若比较失败,目标操作数会被写回原值;若比较成功,则将源操作数写入目标操作数。(处理器绝不会只产生锁定读而不产生锁定写。)
这里的“写周期”是在lock保护下的原子操作的一部分,也就是说,写周期和前面的读、比较操作是不可分割的原子单元,不会被其他线程的lock操作打断。
内容的提问来源于stack exchange,提问作者Linda Turasco

