C++ 在简单情况下同时写入和读取布尔值的危险
我读过一些类似的问题,但其中描述的情况要复杂一些 我在堆中有一个C++ 在简单情况下同时写入和读取布尔值的危险,c++,multithreading,nonatomic,C++,Multithreading,Nonatomic,我读过一些类似的问题,但其中描述的情况要复杂一些 我在堆中有一个bool b初始化为false,还有两个线程。我确实理解使用bools的操作是而不是原子的,但请将问题读到最后 第一个线程只能设置b=true一次,不做任何其他操作。 第二个线程检查循环中的b,如果true则执行一些操作 我是否需要使用一些同步机制(如互斥)来保护b? 如果我不这样做会发生什么?使用ints我可以在同时读写时获得任意值。但是对于bools,只有true和false,我不介意一次得到false,而不是true。这是一种
bool b
初始化为false
,还有两个线程。我确实理解使用bools
的操作是而不是原子的
,但请将问题读到最后
第一个线程只能设置b=true
一次,不做任何其他操作。
第二个线程检查循环中的b
,如果true
则执行一些操作
我是否需要使用一些同步机制(如互斥)来保护b
?
如果我不这样做会发生什么?使用
ints
我可以在同时读写时获得任意值。但是对于bools
,只有true
和false
,我不介意一次得到false
,而不是true
。这是一种潜在的SIGSEGV吗?数据竞争导致未定义的行为。就本标准而言,允许使用符合要求的实施方案
实际上,主要的危险在于,如果不进行同步,编译器将在读卡器循环中观察到足够多的代码,从而判断b
“永不更改”,并优化除第一次读取值以外的所有值。它可以这样做,因为如果它观察到循环中没有同步,那么它知道对值的任何写入都将是数据竞争。优化器可以假定您的程序不会引发未定义的行为,因此可以假定没有来自其他线程的写操作
将b
标记为volatile
将在实践中阻止这种特定的优化,但即使在volatile
对象上,数据竞争也是未定义的行为。调用优化器“看不见”的代码也会在实践中阻止优化,因为它不知道该代码是否修改b
。当然,使用链接时/整个程序优化时,优化器看不到的东西要比只使用编译时优化时少
无论如何,阻止在软件中进行优化并不能阻止在具有非一致缓存的系统上的硬件中发生相同的事情(至少,我是这么说的:其他人认为这是不正确的,通过缓存进行读/写需要volatile
访问。有些实现确实如此).如果你问标准是怎么说的,那么硬件是否会无限期地向你显示一个过时的缓存其实并不重要,因为行为仍然没有定义,因此实现可能会破坏你的代码,而不管这个特定的优化是否会破坏它
我是否需要使用一些同步机制(如互斥)来保护b
如果没有,则存在数据竞争。具有数据竞争的程序具有未定义的行为。此问题的答案与“是否希望您的程序具有定义良好的行为”问题的答案相同
如果我不这样做会发生什么
理论上,任何事情都可能发生。这就是未定义行为的含义。可能发生的最坏情况是“第二个线程”可能永远看不到true
值
编译器可以假设一个程序没有数据竞争(如果它有标准没有定义的行为,那么表现得好像没有就好了).由于第二个线程只从值为false
的变量中读取,并且没有影响这些读取的同步,因此逻辑结论是该值永远不会更改,因此循环是无限的。(在C++11中,一些无限循环具有未定义的行为!)您可能遇到的问题是,我们不知道读卡器线程需要多长时间才能看到更改的值。如果它们位于不同的CPU上,具有单独的缓存,则无法保证,除非您使用内存屏障来同步缓存
在x86上,这是由硬件协议自动处理的,但在其他一些系统上则不会。使布尔值变为volatile就足够了(在x86体系结构上),不需要互斥:
volatile bool b;
以下是一些替代解决方案:
如果您不介意获取一个false
false
,也就是说,那么您不需要保护它,因为您只有一个“writer”。编写布尔应该是原子的。即使它不是原子的,也可能会导致任意值,但不会产生segfault。请注意,“检查循环”听起来像是一个积极的等待。这是一件坏事。请不要因为我问这个问题而责骂我,因为对一些人来说,仅仅提到世界volatile
似乎就会引起愤怒。volatile会对你引用的优化产生任何影响吗?@WhozCraig:volatile
声明变量可以通过任何方式更改源代码,包括硬件,非常正确,它可能永远不会被优化。@WhozCraig如果volatile
与Java中的意思相同,是的,它会有效果。是的,它会有效果,但使用错误的工具进行作业将是一个不必要的低效解决方案。如果某个变量被标记为volatile
,则该变量上的所有读/写操作都将被取消可观察,这意味着它们必须按照您指定的方式出现(它们不能重新排序或优化)。volatile的问题在于它不提供