C++ 11 可以在不同线程中通过引用安全地传递和访问 std::atomics

Posted

技术标签:

【中文标题】C++ 11 可以在不同线程中通过引用安全地传递和访问 std::atomics【英文标题】:C++ 11 can you safely pass and access std::atomics by reference in different threads 【发布时间】:2018-07-11 03:23:44 【问题描述】:

我想知道您是否可以通过引用线程来传递原子,并且 .load 和 .store 操作仍然是线程安全的。例如:

#include <thread>
#include <atomic>
#include <cstdlib>

void addLoop(std::atomic_int& adder)

    int i = adder.load();
    std::srand(std::time(0));
    while(i < 200)
    
        i = adder.load();
        i += (i + (std::rand() % i));
        adder.store(i);
    


void subLoop (std::atomic_int& subber)

    int j = subber.load();
    std::srand(std::time(0));
    while(j < 200)
    
        j = subber.load();
        j -= (j - (std::rand() % j));
        subber.store(j);
    


int main()

    std::atomic_int dummyInt(1);
    std::thread add(addLoop, std::ref(dummyInt));
    std::thread sub(subLoop, std::ref(dummyInt));
    add.join();
    sub.join();
    return 0;

当 addLoop 线程将新值存储到 atomic 时,如果 subLoop 使用 load 和 store 函数访问它,它最终会成为未定义状态吗?

【问题讨论】:

您的代码有问题,` j -= (j - (std::rand() % j));` 可能会使 j=0,然后出现被零除的错误。 使用std::thread 参数的别名与引用单个全局对象的多个线程的工作方式没有任何不同——设置引用相对于启动线程是严格排序的,所以你肯定是别名std::atomic 对象正确。之后,取决于对象如何处理并发访问(对于std::atomic 很好,对于大多数其他类型甚至不要尝试) 【参考方案1】:

根据 [intro.races]/20.2,

如果程序的执行包含两个潜在的并发冲突操作,则该程序的执行包含数据竞争, 至少其中一个不是原子的,也不会发生在另一个之前,除了特殊情况 下面描述的信号处理程序。任何此类数据竞争都会导致未定义的行为。

根据 [intro.races]/2,

如果其中一个修改了内存位置 (4.4) 而另一个读取 或修改相同的内存位置。

通过引用访问原子变量不会引入任何额外的访问或修改,因为引用不占用内存位置。因此,当这些操作通过引用发生时,执行潜在的并发原子操作仍然是安全的。

实际上,在 C++ 的抽象求值模型中,通过对象名称访问对象和通过绑定到该对象的引用变量访问对象没有区别。两者都只是引用对象的左值。

注意std::atomic_init 函数,它是非原子的:

std::atomic<int> x;
void f(std::atomic<int>& r) 
    std::atomic_init(&r, 0);

void g(std::atomic<int>& r) 
    r = 42;

在上面的代码中,如果fg 在不同的线程中运行并且都访问原子变量x,则会发生数据竞争,因为其中一个操作不是原子的。但是,这与像这样触发数据竞争没有什么不同:

std::atomic<int> x;
void f() 
    std::atomic_init(&x, 0);

void g() 
    x = 42;

不涉及引用。

【讨论】:

以上是关于C++ 11 可以在不同线程中通过引用安全地传递和访问 std::atomics的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中通过引用传递时参数的默认值

有人可以解释在 C++ 中通过指针传递和通过引用传递的目的是啥吗?

在 C++ 中通过引用和值传递字符串

为啥我们需要一个默认构造函数来在 C++ 中通过引用传递一个对象?

在python中通过引用传递引用

在 C++ 中通过引用传递对象