pthread_spinlock和boost :: smart_ptr :: spinlock之间的区别?

Ste*_*mer 7 c++ linux performance spinlock

我发现以下自旋锁代码boost::smart_ptr:

bool try_lock()
{
    return (__sync_lock_test_and_set(&v_, 1) == 0);
}
void lock()
{    
    for (unsigned k=0; !try_lock(); ++k)
    {
        if (k<4)
            ; // spin
        else if (k < 16)
            __asm__ __volatile__("pause"); // was ("rep; nop" ::: "memory")
        else if (k < 32 || k & 1)
            sched_yield();
        else
        {
            struct timespec rqtp;
            rqtp.tv_sec  = 0;
            rqtp.tv_nsec = 100;
            nanosleep(&rqtp, 0);
        }
    }
}
void unlock()
{
     __sync_lock_release(&v_);
}
Run Code Online (Sandbox Code Playgroud)

因此,如果我正确地理解这一点,当锁争用时,传入的线程将以指数方式退避,首先疯狂地旋转,然后暂停,然后产生剩余的时间片,最后在睡眠和屈服之间翻转.

我还发现了glibc pthread_spinlock实现,它使用程序集来执行锁定.

#define LOCK_PREFIX "lock;" // using an SMP machine

int pthread_spin_lock(pthread_spinlock_t *lock)
{
    __asm__ ("\n"
       "1:\t" LOCK_PREFIX "decl %0\n\t"
       "jne 2f\n\t"
       ".subsection 1\n\t"
       ".align 16\n"
       "2:\trep; nop\n\t"
       "cmpl $0, %0\n\t"
       "jg 1b\n\t"
       "jmp 2b\n\t"
       ".previous"
       : "=m" (*lock)
       : "m" (*lock));

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我承认我对装配的理解不是很好,所以我不完全理解这里发生的事情.(有人可以解释一下这是做什么的吗?)

但是,我针对boost spinlock和glibc pthread_spinlock运行了一些测试,当内核多于线程时,boost代码优于glibc代码.

另一方面,当线程数多于核心数时,glibc代码更好.

为什么是这样?这两个spinlock实现之间的区别是什么使它们在每个场景中的执行方式不同?

Mic*_*urr 5

你在哪里得到pthread_spin_lock()了问题的实施?它似乎缺少几条重要的线条.

我看到的实现(不是内联汇编 - 它是一个独立的汇编源文件glibc/nptl/sysdeps/i386/pthread_spin_lock.S)看起来很相似,但有两个额外的关键指令:

#include <lowlevellock.h>

    .globl  pthread_spin_lock
    .type   pthread_spin_lock,@function
    .align  16
pthread_spin_lock:
    mov 4(%esp), %eax
1:  LOCK
    decl    0(%eax)
    jne 2f
    xor %eax, %eax
    ret

    .align  16
2:  rep
    nop
    cmpl    $0, 0(%eax)
    jg  1b
    jmp 2b
    .size   pthread_spin_lock,.-pthread_spin_lock
Run Code Online (Sandbox Code Playgroud)

它递减long指向传入的参数,如果结果为零则返回.

否则,结果为非零,这意味着该线程没有获得锁定.所以它执行a rep nop,这相当于pause指令.这是一个"特殊"的nop,它向CPU提示线程处于旋转状态,并且cpu应该以某种方式处理内存排序和/或分支prdiction,以提高这些情况下的性能(我不假装准确地理解在芯片的封面下会发生什么不同 - 从软件的角度来看,与普通老版本没有区别nop.

之后pause再次检查该值 - 如果它大于零,则锁定无人认领,因此它会跳转到函数顶部并尝试再次声明锁定.否则,它会跳到另一个pause.

这种自旋锁和增压版本之间的主要区别是,这一个从来不做事不是票友pause,当它纺纱-没有什么比一个sched_yield()nanosleep().所以线程保持热点.我不确定这在你记下的两个行为中是如何起作用的,但是glibc代码会更贪婪 - 如果一个线程在锁上旋转并且有其他线程准备好运行但是没有可用的核心,则旋转的线程不会帮助等待线程获得任何CPU时间,而Boost版本最终会自动为等待注意的线程让路.