学习线程与锁:Intel x86 CPU硬件层面的原子操作指令有哪些?
Hey there! Great question—grabbing a hardware-level understanding of atomic operations is crucial when diving into threads, locks, and lock-free programming. Let's break down exactly which x86 instructions are atomic, and walk through practical examples.
一、自带原子性的指令(无需LOCK前缀)
These instructions are inherently atomic when accessing properly aligned memory addresses (alignment matches the operand size: e.g., 4-byte alignment for 32-bit values). No extra prefix is needed:
- Load/Store指令:
MOVoperations for single bytes, words, double-words, or quad-words (e.g.,MOV eax, DWORD PTR [mem]orMOV DWORD PTR [mem], ebx)—as long as the memory address is aligned, these are atomic. XCHG指令: This is a special case. EveryXCHGinstruction is atomic, even without theLOCKprefix. The x86 hardware automatically asserts a lock signal forXCHG, making it perfect for simple spinlock implementations.
二、需LOCK前缀的原子复合指令(读-修改-写操作)
For operations that involve reading a value, modifying it, and writing it back (read-modify-write cycles), you must use the LOCK prefix to guarantee atomicity across all CPU cores. These are the workhorses of lock-free synchronization:
- Arithmetic operations:
ADD,SUB,INC,DEC - Bitwise operations:
AND,OR,XOR - Bit manipulation:
BTS(test and set bit),BTR(test and reset bit),BTC(test and complement bit) - Compare-and-swap:
CMPXCHG(the foundation of CAS operations) - Exchange-and-add:
XADD
The LOCK prefix works by leveraging the CPU's cache coherence protocol (like MESI on modern x86) to lock the cache line instead of the entire memory bus—this is far more efficient than old-school bus locking.
三、实用原子操作示例
Let's look at some real-world uses of these instructions:
1. 原子自增计数器
A common use case—safely incrementing a shared counter across threads:
; 原子将内存地址[counter]的值加1 LOCK INC DWORD PTR [counter]
If you need to get the original value before incrementing, XADD is more flexible:
MOV eax, 1 ; 设置要添加的值为1 LOCK XADD DWORD PTR [counter], eax ; 执行后:eax = 原来的counter值,[counter] = 原来的值 +1
2. CAS(比较并交换)操作
CAS is the core of most lock-free data structures. It checks if a memory location matches an expected value, and only updates it if so:
; 假设: ; ecx = 目标内存地址 ; eax = 期望值(我们认为当前内存中的值应该是这个) ; edx = 要替换的新值 LOCK CMPXCHG DWORD PTR [ecx], edx ; 结果: ; - 如果[ecx] == eax:内存值被替换为edx,ZF标志位设为1 ; - 如果不相等:eax被更新为[ecx]的当前值,ZF设为0
3. 原子位操作
Atomic bit operations are great for lightweight flags (like spinlock status):
; 原子将[flag]的第3位(从0开始计数)置为1 LOCK BTS DWORD PTR [flag], 3 ; 操作后,CF标志位会返回该位原来的状态(0或1)
关键注意事项
- Alignment matters: While modern x86 CPUs can handle some unaligned atomic accesses, it's still best practice to align memory to the operand size (e.g., 4 bytes for 32-bit values). Unaligned accesses can cause performance hits or compatibility issues on older hardware.
LOCKprefix overhead: TheLOCKprefix is cheap on modern CPUs thanks to cache locking, but overusing it can lead to contention. Always prefer the minimal atomic operation needed for your use case.XCHGis always atomic: You don't need to addLOCKtoXCHG—the hardware handles it automatically. This is why many spinlock implementations useXCHGto atomically swap a lock variable.
内容的提问来源于stack exchange,提问作者user1187968

