x86自旋锁使用cmpxchg

我是使用gcc内联汇编的新手,并且想知道在x86多核机器上是否可以实现自旋锁(无竞态条件)(使用AT&T语法):

spin_lock:
mov 0 eax
lock cmpxchg 1 [lock_addr]
jnz spin_lock
ret

spin_unlock:
lock mov 0 [lock_addr]
ret

你有正确的想法,但你的创业板已经破裂:

cmpxchg不能使用立即数操作数,只能注册。

lock不是mov的有效前缀。 mov到一个对齐的地址在x86上是原子的,所以你不需要lock 。

我使用AT&T语法已经有一段时间了,希望我能记住所有的东西:

spin_lock:
    xorl   %ecx, %ecx
    incl   %ecx            # newVal = 1
spin_lock_retry:
    xorl   %eax, %eax      # expected = 0
    lock; cmpxchgl %ecx, (lock_addr)
    jnz    spin_lock_retry
    ret

spin_unlock:
    movl   $0,  (lock_addr)    # atomic release-store
    ret

请注意,GCC具有原子构建,因此您实际上不需要使用内联asm来完成此操作:

void spin_lock(int *p)
{
    while(!__sync_bool_compare_and_swap(p, 0, 1));
}

void spin_unlock(int volatile *p)
{
    asm volatile ("":::"memory"); // acts as a memory barrier.
    *p = 0;
}

正如Bo在下面所说的,锁定指令会产生一定的成本:您使用的每个锁定指令必须获得对cache行的独占访问权,并在lock cmpxchg运行时将其lock cmpxchg ,这可以延迟解锁线程,特别是在多个线程正在等待锁定的情况下。 即使没有很多CPU,它仍然很容易,并且值得优化:

void spin_lock(int volatile *p)
{
    while(!__sync_bool_compare_and_swap(p, 0, 1))
    {
        // spin read-only until a cmpxchg might succeed
        while(*p) _mm_pause();  // or maybe do{}while(*p) to pause first
    }
}

pause指令对于超线程CPU的性能至关重要,因为当你有这样的代码旋转时 - 它允许第二个线程在第一个线程旋转时执行。 在不支持pause CPU上,它被视为nop 。

当离开旋转循环时, pause还可以防止记忆顺序错误猜测,当它终于到了再次做实际工作的时候时。

请注意,真正的自旋锁实现不会永远旋转; 他们回到操作系统辅助的睡眠和通知机制。 他们也可能采取措施来提高公平性,以及cmpxchg / pause循环无法做的其他许多事情。


这会减少内存总线上的争用:

void spin_lock(int *p)
{
    while(!__sync_bool_compare_and_swap(p, 0, 1)) while(*p);
}

猜你喜欢

转载自blog.csdn.net/linuxheik/article/details/82491468