多个线程能否安全地将相同的值同时写入同一个变量?
Can multiple threads write the same value to the same variable at the same time safely?
多个线程能否安全地同时向同一个变量写入相同的值?
举个具体的例子——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");
}
这是一道理论题;我想知道它是否肯定总是有效,而不是它在实践中是否有效(或者像这样编写代码是否是个好主意:))。如果有人能指出标准的相关部分,我将不胜感激。根据我的经验,它在实践中总是有效,但不知道它是否保证有效我总是使用 std::atomic
代替 - 我想知道对于这种特定情况这是否是绝对必要的。
没有
您需要同步对这些变量的访问,通过使用互斥或使它们成为原子。
写入相同值时没有豁免。您不知道编写该值涉及哪些步骤(这是潜在的实际问题),标准也不知道,这就是代码具有未定义行为的原因……这意味着您的编译器可以对您的程序造成绝对混乱(这就是您需要避免的 真实 问题)。
有人会告诉您某某架构可以保证原子写入这些大小的变量。但这并没有改变 UB 方面。
您要查找的段落是:
[intro.races/2]
: Two expression evaluations conflict if one of them modifies a memory location ([intro.memory]) and the other one reads or modifies the same memory location.
[intro.races/21]
: […] The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, […]. Any such data race results in undefined behavior.
...以及周围的措辞。该部分实际上非常深奥,但您实际上不需要解析它,因为这是经典的教科书数据竞赛,您可以在任何有关编程的书籍中读到它。
亮度是正确的,从标准的角度来看是正确的。
但我会给你另一个观点,为什么从硬件架构的角度来看这不是一个好主意。
如果没有内存屏障(原子、互斥等...),您可能会遇到所谓的缓存一致性问题。在多核或多处理器机器上,您的两个线程都可以将 x
设置为 true
,但您的主线程可能会打印 false
,即使您的编译器没有隐藏 x
写入寄存器。那是因为主线程使用的硬件缓存还没有更新到 x
从它所在的任何缓存行无效。 C++ 提供的原子类型和锁守卫(以及无数 OS 原语)被实现来解决这个问题。
无论如何,google for Cache Coherence Problem and Cache Coherence Multicore. And for a particular architecture implementation of how atomic transactions are implemented, look up the Intel LOCK prefix.
多个线程能否安全地同时向同一个变量写入相同的值?
举个具体的例子——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");
}
这是一道理论题;我想知道它是否肯定总是有效,而不是它在实践中是否有效(或者像这样编写代码是否是个好主意:))。如果有人能指出标准的相关部分,我将不胜感激。根据我的经验,它在实践中总是有效,但不知道它是否保证有效我总是使用 std::atomic
代替 - 我想知道对于这种特定情况这是否是绝对必要的。
没有
您需要同步对这些变量的访问,通过使用互斥或使它们成为原子。
写入相同值时没有豁免。您不知道编写该值涉及哪些步骤(这是潜在的实际问题),标准也不知道,这就是代码具有未定义行为的原因……这意味着您的编译器可以对您的程序造成绝对混乱(这就是您需要避免的 真实 问题)。
有人会告诉您某某架构可以保证原子写入这些大小的变量。但这并没有改变 UB 方面。
您要查找的段落是:
[intro.races/2]
: Two expression evaluations conflict if one of them modifies a memory location ([intro.memory]) and the other one reads or modifies the same memory location.
[intro.races/21]
: […] The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, […]. Any such data race results in undefined behavior.
...以及周围的措辞。该部分实际上非常深奥,但您实际上不需要解析它,因为这是经典的教科书数据竞赛,您可以在任何有关编程的书籍中读到它。
亮度是正确的,从标准的角度来看是正确的。
但我会给你另一个观点,为什么从硬件架构的角度来看这不是一个好主意。
如果没有内存屏障(原子、互斥等...),您可能会遇到所谓的缓存一致性问题。在多核或多处理器机器上,您的两个线程都可以将 x
设置为 true
,但您的主线程可能会打印 false
,即使您的编译器没有隐藏 x
写入寄存器。那是因为主线程使用的硬件缓存还没有更新到 x
从它所在的任何缓存行无效。 C++ 提供的原子类型和锁守卫(以及无数 OS 原语)被实现来解决这个问题。
无论如何,google for Cache Coherence Problem and Cache Coherence Multicore. And for a particular architecture implementation of how atomic transactions are implemented, look up the Intel LOCK prefix.