提问者:小点点

哪种自旋锁方法更有效:重试test_and_set(),还是在test()上自旋只读?


哪种自旋锁方法更好(在效率方面)?

#include <atomic>

#define METHOD 1


int main( )
{
    std::atomic_flag lock { };

#if METHOD == 1
    while ( lock.test_and_set( std::memory_order_acquire ) )
    {
        while ( lock.test( std::memory_order_relaxed ) );
    }
#else
    while ( lock.test_and_set( std::memory_order_acquire ) );
#endif

    lock.clear( std::memory_order_release );
}

这个例子来自cp首选。当我们在外循环中添加/删除对test(std::memory_order_relaxed)的调用时会发生什么?

我看到这两个方法之间生成的代码有明显的差异(这里)。


共1个答案

匿名用户

一般来说,在. test()上以只读方式旋转的版本是最好的,而不是从试图解锁它的线程窃取缓存行的所有权。特别是如果自旋锁与任何其他数据位于同一缓存行中,就像锁所有者可能正在读取的数据一样,你这样做会造成更多更糟糕的错误共享。

此外,如果多个线程在同一个自旋锁上自旋等待,您不希望它们在内核之间的互连上浪费带宽ping包含锁的缓存行。(如果多个线程自旋经常发生,那么纯自旋锁通常是一个糟糕的选择。通常,您希望通过OS辅助睡眠/唤醒最终将CPU交给另一个线程,例如通过futex。C 20。等待()。notify_one()可以做到这一点,或者只是使用std::互斥锁std::shared_mutex的良好实现。)。

有关更多详细信息,请参阅:

  • 我对通过内联汇编进行内存操作的锁的回答
  • cmpxchg是否在失败时写入目标缓存行?如果没有,它是否比xchg更适合自旋锁?

不幸的是,C缺少一个可移植的函数,比如Rust的core::提示::spin_loop,它可以编译成x86上的暂停指令,或者其他ISA上的等效指令。

因此,只读循环将在具有超线程的CPU上浪费更多的执行资源(从另一个逻辑核心窃取它们),但如果其他任何东西正在读取该行,则浪费更少的存储缓冲区条目和更少的非核心流量。特别是如果您有多个线程在同一个锁上自旋等待,ping ponging缓存行!

如果您不介意#ifdef__amd64__/#include