为什么自旋锁中的第二次自旋可以提高性能?

Why second spin in Spinlock gives performance boost?

这是使用 std::atomic_flag 实现的基本自旋锁。
book 的作者声称 lock() 中的第二个 while 提高了性能。

class Spinlock
{
    std::atomic_flag flag{};
public:
    void lock() {
        while (flag.test_and_set(std::memory_order_acquire)) {
            while (flag.test(std::memory_order_acquire)); //Spin here
        }
    }
    void unlock() {
        flag.clear(std::memory_order_release);
    }
};

The reason we use test() in an extra inner loop is performance: test() doesn't invalidate cache line, whereas test_and_set() does.

有人可以详细说明一下这句话吗? test还是读操作,需要从内存中读取对吗?

读取内存地址不会清除缓存行。

写作可以。

所以在现代计算机中,有 RAM,CPU“周围”有多层缓存(它们被称为 L1、L2 和 L3 缓存,但重要的是它们是层,CPU 在中间)。在 multi-core 系统中,通常外层是共享的;最内层通常不是,并且特定于给定的 CPU.

清除缓存行意味着通知持有此内存的所有其他缓存“您拥有的数据可能已过时,将其丢弃”。

测试并设置为 true 并原子地写入 returns 旧值。它清除缓存行,因为它写入。

测试不写。如果你有另一个线程与这个线程不同步,它读取这个内存的缓存就不用戳了。

外层循环写入true,替换为false退出。内循环一直等到有假可见,才落入外循环。内部循环不需要清除每个其他 cpu 的原子标志值的缓存状态,但外部必须这样做(因为它可以将 false 更改为 true)。由于旋转可能会持续一段时间,因此避免连续清除缓存似乎是个好主意。

(原子)存储需要独占访问缓存行,但普通原子(释放)存储仍然相对有效,因为涉及存储缓冲区 'caching'; IE。不需要立即访问缓存行。

在自旋锁示例中,存储 (test_and_set) 是一个原子操作 read-modify-write (RMW),由于其性质,它的速度明显较慢;它在一次操作中读取和写入。
这需要立即访问缓存行,而存储缓冲区中仍未决的任何内容都必须在继续之前被刷新。 此外,必须锁定缓存行以确保读写之间的独占访问。 这很昂贵,但自旋锁需要它来保证独占锁访问。

当多个线程执行同一个 test_and_set 操作时,缓存行会在 CPU 个内核之间不断跳动。
内部循环(使用原子读取)是一种优化,因为多个 threads/cores 可以 read-only 访问同一缓存行(MESI 缓存一致性协议中的 'SHARED' )。 一旦 inner-loop test 操作读取到 false,outer-loop 中的 test_and_set 就获得了锁,只有在那个时候,才需要 std::memory_order_acquire。 因此,在内部循环中的 test 操作上使用 'acquire' 是毫无意义的,并且对较弱的体系结构感到悲观,其中 memory_order_acquire 需要额外的 CPU 指令(例如 PowerPC)。
由于内循环都是关于优化的,所以它应该使用 std::memory_order_relaxed.

    void lock() {
        while (flag.test_and_set(std::memory_order_acquire)) {
            while (flag.test(std::memory_order_relaxed)); //Spin here
        }
    }

为了进一步优化,可以使用独立的栅栏,以便 CPU 栅栏指令 在线程获得锁定标志后调用。

class Spinlock
{
    std::atomic_flag flag{};
public:
    void lock() {
        while (flag.test_and_set(std::memory_order_relaxed)) {
            while (flag.test(std::memory_order_relaxed)); //Spin here
        }
        std::atomic_thread_fence(std::memory_order_acquire);
    }
    void unlock() {
        flag.clear(std::memory_order_release);
    }
};

有一个内部循环是一种有用的优化吗? - 也许..它不会造成伤害,但如果经常到达内循环,就会存在一些争用,自旋锁可能不是正确的工具。