Kar*_*aru 5 c++ multithreading thread-safety data-race
多个线程可以安全地同时将相同的值写入相同的变量吗?
对于一个特定的例子——下面的代码是否由 C++ 标准保证编译,在没有未定义行为的情况下运行并在每个符合标准的系统上打印“true”?
#include <cstdio>
#include <thread>
int main()
{
bool x = false;
std::thread one{[&]{ x = true; }};
std::thread two{[&]{ x = true; }};
one.join();
two.join();
std::printf(x ? "true" : "false");
}
Run Code Online (Sandbox Code Playgroud)
这是一个理论问题;我想知道它是否总是有效,而不是在实践中是否有效(或者编写这样的代码是否是个好主意:))。如果有人能指出标准的相关部分,我将不胜感激。根据我的经验,它在实践中总是有效,但不知道它是否保证有效,我总是使用它std::atomic- 我想知道这对于这种特定情况是否绝对必要。
Lig*_*ica 11
不。
您需要通过使用互斥锁或使它们成为原子来同步对这些变量的访问。
写入相同值时没有豁免。您不知道编写该值涉及哪些步骤(这是潜在的实际问题),标准也不知道为什么代码具有未定义的行为……这意味着您的编译器可以对您的程序造成绝对的混乱(这就是您需要避免的真正问题)。
有人会过来告诉你,某某架构保证了对这些大小变量的原子写入。但这并没有改变 UB 方面。
您正在寻找的段落是:
[intro.races/2]:如果其中一个修改内存位置 ([intro.memory]) 而另一个读取或修改相同的内存位置,则两个表达式计算会发生冲突。
[intro.races/21]: [...] 如果一个程序包含两个潜在的并发冲突操作,则该程序的执行包含数据竞争,[...]。任何此类数据竞争都会导致未定义的行为。
......以及周围的措辞。该部分实际上非常深奥,但您实际上并不需要解析它,因为这是一个经典的教科书数据竞赛,您可以在任何有关编程的书籍中阅读。
从标准的角度来看,亮度是正确且准确的。
但我会从另一个角度告诉您为什么从硬件架构的角度来看这不是一个好主意。
如果没有内存屏障(原子、互斥等),您可能会遇到所谓的缓存一致性问题。在多核或多处理器计算机上,您的两个线程都可以设置为x,但即使您的编译器没有存储到寄存器中true,您的主线程也可能会打印。这是因为主线程使用的硬件缓存尚未更新为已从其打开的任何缓存行中失效。C++ 提供的原子类型和锁保护(以及无数的操作系统原语)是为了解决这个问题而实现的。falsexx
无论如何,谷歌搜索Cache Coherence Problem和Cache Coherence Multicore。对于如何实现原子事务的特定架构实现,请查找Intel LOCK 前缀。