哪种自旋锁方法更好(在效率方面)?
#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 );
}
这个例子来自cppreference。当我们在外循环中添加/删除对
test(std::memory_order_relaxed)
的调用时会发生什么?
我看到两种方法之间生成的代码有显着差异(here)。
一般情况下,在
.test()
上旋转只读版本是最好的,而不是从试图解锁它的线程中窃取缓存行的所有权。特别是如果自旋锁与任何其他数据位于相同的缓存行中,比如锁所有者可能正在读取的数据,你就会以这种方式创建更多更糟糕的错误共享。
此外,如果多个线程在同一个自旋锁上自旋等待,您不希望它们在内核之间的互连上浪费带宽,ping-ponging 包含锁的缓存行。 (如果经常发生多线程自旋,那么纯自旋锁通常是一个糟糕的选择。通常您希望最终通过操作系统辅助睡眠/唤醒将 CPU 交给另一个线程,例如通过
futex
。C++20 .wait()
和 .notify_one()
可以做到这一点,或者只是使用 std::mutex
或 std::shared_mutex
的良好实现。)。
详情见:
core::hint::spin_loop
这样的可移植函数,它将在 x86 上编译为 pause
指令,或在其他 ISA 上等效。
因此,只读循环将在具有超线程的 CPU 上浪费更多的执行资源(从其他逻辑核心窃取它们),但如果其他任何东西正在读取该行,则浪费更少的存储缓冲区条目和更少的核外流量。特别是如果你有多个线程自旋等待同一个锁,ping-ponging 缓存行!
如果你不介意
#ifdef __amd64__
/ #include <immintrin.h>
for _mm_pause()
,那么你也可以拥有这个优势。