如果跨 2 个线程使用,是否值得将 size_t 声明为 std::atomic?

Is is worth to declare a size_t as std::atomic if used across 2 threads?

我有一个 size_t 变量,它由 std::thread 更新并由另一个 std::thread 读取。

我知道我可以互斥保护读写。 但是,如果我将 size_t 设为 std::atomic<size_t> 是否相同或有益?

是的,这是值得的。事实上,如果多个线程使用同一个变量并且至少有一个正在写入变量,则必须使用 std::atomic 或同步访问非原子。 不遵守此规则是数据争用未定义行为。

根据您对 std::size_t 的使用,编译器可以假定非原子变量和其他非同步变量不会从其他线程更改并相应地优化代码。这可能会导致 Bad Things™ 发生。

我常用的例子是使用非原子布尔值的循环:

// make keepRunning an std::atomic<bool> to avoid endless loop
bool keepRunning {true};
unsigned number = 0;

void stop()
{
    keepRunning = false;
}

void loop()
{
    while(keepRunning) {
        number += 1;
    }
}

在启用优化的情况下编译此代码时,GCC 和 Clang 都只会检查 keepRunning 一次,然后开始无限循环。 有关生成的汇编程序输出,请参阅 https://godbolt.org/z/GYMiLE

即他们将其优化为 if (keepRunning) infinite_loop;,将负载提升到循环之外。因为它是非原子的,所以他们可以假设没有其他线程可以写入它。有关同一问题的更详细信息,请参阅

请注意,此示例仅在循环体足够简单的情况下显示错误。然而,未定义的行为仍然存在,应该通过使用 std::atomic 或同步来避免。


在这种情况下,您可以将 std::atomic<bool>std::memory_order_relaxed 一起使用,因为您不需要任何同步或排序。写入或读取线程中的其他操作。这将为您提供原子性(无撕裂)和值 可以 异步更改的假设,而不会使编译器使用任何 asm 屏障指令来创建更多的 wrt 排序。其他操作。

因此,在没有任何同步的情况下使用原子是可能且安全的,甚至没有 在编写器和 reader 之间创建 同步 seq_cst 或 acquire/release 加载和存储都可以。您可以使用此同步来安全地共享非原子变量或数组,例如当指针为非 NULL 时,reader 读取 atomic<int*> buffer

但是如果只有原子变量本身是共享的,你可以只让 readers 读取当前值,而不关心同步。如果您不需要重新读取短循环的每次迭代,您可能希望将它读入本地临时文件,每次函数调用只需一次。