我有一个size_t
变量,它由std::thread
更新并由另一个std::thread
读取。
我知道我可以互斥保护读写。 但是,如果我将size_t
std::atomic<size_t>
,它会是一样的还是有益的?
是的,这是值得的。事实上,如果多个线程使用相同的变量并且至少有一个线程正在写入该变量,则必须使用std::atomic
或同步对非原子的访问。 不遵循此规则是数据争用未定义的行为。
根据您对std::size_t
的使用,编译器可以假定非原子变量和其他非同步变量不会从其他线程更改,并相应地优化代码。这可能会导致坏事™发生。
我通常的例子是使用非原子布尔值的循环:
// 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;
,将负载吊出环路。 因为它是非原子的,所以他们被允许假设没有其他线程可以写入它。 有关同一问题的更详细信息,请参阅卡在优化模式下但在 -O0 中正常运行的多线程程序。
请注意,此示例仅在循环体足够简单时才显示错误。但是,未定义的行为仍然存在,应通过使用 std::atomic 或同步来避免。
在这种情况下,您可以将std::atomic<bool>
与std::memory_order_relaxed
一起使用,因为您不需要任何同步或排序 wrt。 写入或读取线程中的其他操作。 这将给你原子性(没有撕裂(和假设值可以异步更改,而不会使编译器使用任何asm屏障指令来创建更多的排序wrt。其他操作。
因此,在没有任何同步的情况下使用原子是可能的,而且是安全的,甚至不需要像seq_cst或获取/释放加载和存储那样在写入器和读取器之间创建同步。 您可以使用此同步安全地共享非原子变量或数组,例如,与指针非 NULL 时读取的atomic<int*> buffer
共享。
但是,如果只共享原子变量本身,则可以让读者读取当前值,而不关心同步。 如果您不需要重新读取短循环的每次迭代,则可能需要将其读入本地临时,每个函数调用只需一次。