返回

解决 Windows x64 上 std::atomic<T> 的锁定争用:MOV 指令的应用

windows

用 MOV 指令封装 std::atomic 以避免 Windows x64 上的锁定争用

std::atomic 的挑战

std::atomic 是一个功能强大的库,用于在多线程环境中管理共享数据,提供一系列安全更新和读取变量的原子操作。然而,在某些情况下,它的实现会带来额外的开销或与特定硬件架构的兼容性问题。

Windows x64 上的困境

在 Windows x64 系统上,std::atomic 的 compare_exchange_strong() 方法使用 CMPXCHG8B 指令执行 8 字节原子更新。但对于包含 16 字节的数据类型,这可能会导致锁定争用,从而降低性能。

MOV 指令的替代方案

一种替代方法是使用 MOV 指令,它允许在不使用锁定前缀的情况下执行 16 字节原子更新。虽然 MOV 指令通常用于非原子操作,但它在某些情况下可以作为原子操作的替代方案。

封装 std::atomic

通过将 std::atomic 封装在自定义类中,我们可以劫持 compare_exchange_strong() 方法,使用 MOV 指令代替。这种方法既灵活又高效,因为它避免了锁定争用,同时仍然确保了原子操作的语义。

示例实现

struct Byte16
{
    int64_t a, b;
};

struct AtomicByte16
{
    std::atomic<Byte16> data;

    bool compare_exchange_strong(const Byte16& expected, const Byte16& desired)
    {
        Byte16 current = data.load(std::memory_order_relaxed);
        if (current == expected)
        {
            // 使用 MOV 指令进行原子更新
            __asm mov eax, desired
            __asm mov edx, data
            __asm mov [edx], eax
            return true;
        }
        return false;
    }
};

在这个示例中,AtomicByte16 类封装了一个 std::atomic 实例。compare_exchange_strong() 方法已被重写,以使用 MOV 指令执行原子更新,避免了锁定争用,同时仍然提供原子语义。

结论

通过利用 MOV 指令的灵活性以及 std::atomic 的封装能力,我们可以克服 Windows x64 上 std::atomic 实现的限制。这种方法提供高效、无锁的 16 字节原子更新,而无需诉诸手动原子操作。

常见问题解答

1. MOV 指令是否在所有情况下都比 std::atomic 更快?

不,MOV 指令的性能优势仅限于某些数据类型和硬件架构。在其他情况下,std::atomic 的实现可能更有效。

2. 使用 MOV 指令有什么缺点?

MOV 指令不能保证原子性,除非在某些特定条件下使用。因此,需要谨慎使用,并确保在所有情况下都能维护数据完整性。

3. std::atomic 的其他替代方案是什么?

除了 MOV 指令外,还有其他替代方案,例如 Windows 的 InterlockedCompareExchange() 函数和 Linux 的 __atomic_compare_exchange() 函数。

4. 封装 std::atomic 有什么好处?

封装 std::atomic 允许我们根据特定的需求定制原子操作的行为。例如,我们可以实现无锁更新、自定义内存排序或在不同平台之间提供一致的 API。

5. 在使用 MOV 指令时应该采取哪些预防措施?

在使用 MOV 指令时,需要小心避免数据损坏。例如,应该确保数据的对齐与处理器架构兼容,并且应该使用内存屏障来确保数据在所有线程中可见。