c++ std::atomic 变量的线程同步问题

fre*_*edo 6 atomic c++11

以下程序在偶尔打印“坏”输出时给了我意想不到的行为。这两个线程应该使用两个 std::atomic 变量“s_lock1”和“s_lock2”进行同步。在 func2 中,为了将 's_var' 变量设置为 1,它必须在 's_lock2' 中原子地存储一个非零值,并且另一个线程 (func1) 必须尚未更新 's_lock1' 变量。然而,在 func1 中,它以某种方式打印了意外的“坏”输出。s_lock2.load() 语句似乎返回 false。这个代码片段有什么问题吗?这是与内存排序有关的问题吗?

我在安装了 Centos 7 的 8 核 Linux 服务器上运行它。任何帮助是极大的赞赏。

#include <iostream>
#include <thread>
#include <atomic>
#include <unistd.h>

std::atomic_uint s_lock1 = 0;
std::atomic_uint s_lock2 = 0;
std::atomic_uint s_var = 0;

static void func1()
{
    while (true) {
        s_lock1.store(1, std::memory_order_release);
        if (s_lock2.load(std::memory_order_acquire) != 0) {
            s_lock1.store(0, std::memory_order_release);
            continue;
        }
        if (s_var.load(std::memory_order_acquire) > 0) {
            printf("bad\n");
        }
        usleep(1000);
        s_lock1.store(0, std::memory_order_release);
    }
}

static void func2()
{
    while (true) {
        s_lock2.store(1, std::memory_order_release);
        if (s_lock1.load(std::memory_order_acquire) != 0) {
            s_lock2.store(0, std::memory_order_release);
            continue;
        }
        s_var.store(1, std::memory_order_release);
        usleep(5000);
        s_var.store(0, std::memory_order_release);
        s_lock2.store(0, std::memory_order_release);
    }
}

int main()
{
    std::thread t1(func1);
    std::thread t2(func2);
    t1.join();
    t2.join();
}
Run Code Online (Sandbox Code Playgroud)

Max*_*kin 5

由于 Intel CPU 中的存储缓冲区,此锁定算法可能会中断:存储不会直接进入 1 级缓存,而是在存储缓冲区中排队一段时间,因此在此期间对另一个 CPU 不可见:

为了实现指令执行的性能优化,IA-32 架构允许背离 Pentium 4、Intel Xeon 和 P6 系列处理器中称为处理器排序的强排序模型。这些处理器排序变体(此处称为内存排序模型)允许性能增强操作,例如允许读取先于缓冲写入。这些变体中的任何一个的目标都是提高指令执行速度,同时保持内存一致性,即使在多处理器系统中也是如此。

需要刷新存储缓冲区才能通过使用std::memory_order_seq_cstfor 存储到锁定(加载和存储的默认内存顺序s_lock1 = 1;,例如,您可以这样做)来使此锁定工作。std::memory_order_seq_cstfor stores 导致编译器在 store 之后生成xchg指令或插入mfence指令,这两者都使 store 的效果对其他 CPU 可见:

原子操作memory_order_seq_cst不仅以与释放/获取排序相同的方式标记内存排序(在一个线程中存储之前发生的所有事情都会在执行加载的线程中成为可见的副作用),而且还建立了所有的单个总修改顺序如此标记的原子操作。对于多个生产者-多个消费者的情况,顺序排序可能是必要的,在这种情况下,所有消费者都必须观察以相同顺序发生的所有生产者的动作。总顺序排序需要所有多核系统上的完整内存栅栏 CPU 指令。这可能会成为性能瓶颈,因为它会迫使受影响的内存访问传播到每个内核。

工作示例:

std::atomic<unsigned> s_lock1{0};
std::atomic<unsigned> s_lock2{0};
std::atomic<unsigned> s_var{0};

void func1() {
    while(true) {
        s_lock1.store(1, std::memory_order_seq_cst);
        if(s_lock2.load(std::memory_order_seq_cst) != 0) {
            s_lock1.store(0, std::memory_order_seq_cst);
            continue;
        }
        if(s_var.load(std::memory_order_relaxed) > 0) {
            printf("bad\n");
        }
        usleep(1000);
        s_lock1.store(0, std::memory_order_seq_cst);
    }
}

void func2() {
    while(true) {
        s_lock2.store(1, std::memory_order_seq_cst);
        if(s_lock1.load(std::memory_order_seq_cst) != 0) {
            s_lock2.store(0, std::memory_order_seq_cst);
            continue;
        }
        s_var.store(1, std::memory_order_relaxed);
        usleep(5000);
        s_var.store(0, std::memory_order_relaxed);
        s_lock2.store(0, std::memory_order_seq_cst);
    }
}

int main() {
    std::thread t1(func1);
    std::thread t2(func2);
    t1.join();
    t2.join();
}
Run Code Online (Sandbox Code Playgroud)