Wikipedia为使用x86 XCHG命令的自旋锁提供的示例实现是:
; Intel syntax
locked: ; The lock variable. 1 = locked, 0 = unlocked.
dd 0
spin_lock:
mov eax, 1 ; Set the EAX register to 1.
xchg eax, [locked] ; Atomically swap the EAX register with
; the lock variable.
; This will always store 1 to the lock, leaving
; the previous value in the EAX register.
test eax, eax ; Test EAX with itself. Among other things, this will
; set the processor's Zero Flag if EAX is 0.
; If EAX is 0, then the lock was unlocked and
; we just locked it.
; Otherwise, EAX is 1 and we didn't acquire the lock.
jnz spin_lock ; Jump back to the MOV instruction if the Zero Flag is
; not set; the lock was previously locked, and so
; we need to spin until it becomes unlocked.
ret ; The lock has been acquired, return to the calling
; function.
spin_unlock:
mov eax, 0 ; Set the EAX register to 0.
xchg eax, [locked] ; Atomically swap the EAX register with
; the lock variable.
ret ; The lock has been released.
从这里https://en.wikipedia.org/wiki/Spinlock#Example_implementation
我不明白的是为什么解锁需要原子。有什么问题
spin_unlock:
mov [locked], 0
解锁确实需要释放语义学来正确保护临界区。但是它不需要顺序一致性。原子性不是真正的问题(见下文)。
所以是的,在x86上,一个简单的存储是安全的,而glibc的pthread_spin_unlock
做到了这一点::
movl $1, (%rdi)
xorl %eax, %eax
retq
另请参阅我在本答案中编写的一个简单但可能可用的x86自旋锁实现,它使用带有暂停
指令的只读自旋循环。
此代码可能改编自位域版本。
使用btr
解锁位字段中的零个标志是不安全的,因为它是包含字节(或包含自然对齐的4字节dword或2字节word)的非原子读取-修改-写入。
所以也许写这篇文章的人没有意识到在x86上,与大多数ISA一样,对齐地址的简单存储是原子的。但是x86拥有弱有序ISA所没有的是,每个存储都有释放语义学。释放锁的xchg
使得每次解锁都有一个完整的内存屏障,这超出了正常的锁定语义学。(尽管在x86上,获取锁将是一个完整的障碍,因为没有xchg
或其他lock
ed指令,就没有办法进行原子RMW或原子比较和交换,这些都是完整的障碍,比如mgrid
。)
解锁存储在技术上不需要是原子的,因为我们只存储零或1,所以只有较低的字节重要。例如,我认为如果锁未对齐并跨越缓存行边界拆分,它仍然可以工作。撕裂可能会发生,但无关紧要,真正发生的是锁的低字节被原子修改,操作总是将零放入较高的3个字节。
如果您想返回旧值以捕获双重解锁错误,更好的实现将单独加载和存储:
spin_unlock:
;; pre-condition: [locked] is non-zero
mov eax, [locked] ; old value, for debugging
mov dword [locked], 0 ; On x86, this is an atomic store with "release" semantics.
;test eax,eax
;jz double_unlocking_detected ; or leave this to the caller
ret
的意义
spin_unlock:
mov eax, 0 ; Set the EAX register to 0.
xchg eax, [locked] ; Atomically swap the EAX register with
; the lock variable.
ret ; The lock has been released.
不仅要解锁,还要用正确的返回值填充eax(如果解锁则为1,否则为0)
如果在调用spin_unlock之前没有获得锁(在这种情况下,[锁定]的值为0),spin_unlock应该返回0