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

sch*_*dge 4 c++ multithreading atomic pass-by-reference c++11

我想知道您是否可以通过引用线程来传递原子,并且 .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;
}
Run Code Online (Sandbox Code Playgroud)

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

Bri*_*ian 5

根据 [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;
}
Run Code Online (Sandbox Code Playgroud)

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

std::atomic<int> x;
void f() {
    std::atomic_init(&x, 0);
}
void g() {
    x = 42;
}
Run Code Online (Sandbox Code Playgroud)

不涉及引用的地方。