atomic<bool>
是不是多余的,因为bool
本身就是原子性的?我认为部分修改布尔值是不可能的。什么时候我才真正需要使用atomic<bool>
而不是bool
呢?
不是所有C++类型都天生"原子的",除非它是std::atomic*
-something。这是因为标准规定如此。
实际上,在操作std::atomic<bool>
时发出的硬件指令可能(也可能不会)与普通bool
相同,但是原子性是一个更大的概念,具有更广泛的影响(例如,限制编译器重新排序)。此外,一些操作(如否定)被重载到原子操作上,以创建与非原子变量的本机非原子读取-修改-写入序列截然不同的指令。
std::atomic_flag
是唯一的例外,尽管它的名称也以atomic开头。 - yngcccstd::atomic*
而不是std::atomic<*>
的原因。 - Sebastian Mach请记住 内存屏障(memory barriers)。虽然可能无法部分更改 bool
,但可能会在多处理器系统中有多个副本并且一个线程即使在另一个线程将其更改为新值后仍可以看到旧值。原子操作引入内存屏障,因此这是不可能的。
volatile
能够解决多处理器问题吗? - Vincent Xuevolatile
关键字的理解。Java中的volatile
关键字确实控制内存屏障,但其行为与C中的volatile
关键字有很大不同,后者没有控制内存屏障的作用。此问题在这篇回答中有进一步的解释。 - PaceC++的原子类型处理三个潜在的问题。首先,如果操作需要多个总线操作(并且这可能发生在bool
上,具体取决于它的实现方式),则读取或写入可能会被任务切换中断。其次,读取或写入可能仅会影响执行该操作的处理器关联的缓存,而其他处理器的缓存中可能有不同的值。第三,如果操作不影响结果,则编译器可以重新排列操作的顺序(约束条件有些复杂,但现在足够了)。
您可以通过对使用的类型如何实现进行假设、显式刷新缓存以及使用特定于编译器的选项来防止重排序(不,除非您的编译器文档说它会这样做,否则volatile
不能做到这一点)来解决这三个问题。
但为什么要这样做呢?atomic
已经为您处理了这些问题,并且可能比您自己做得更好。
考虑一个比较和交换的操作:
bool a = ...;
bool b = ...;
if (a)
swap(a,b);
当我们读取a时,得到true,另一个线程可以设置a为false,然后我们交换(a,b),所以退出时b为false,尽管进行了交换。
使用std::atomic ::compare_exchange
,我们可以将整个if/swap逻辑 原子化,这样其他线程就不能在if和swap之间将a设置为false(无需锁定)。在这种情况下,如果进行了交换,则退出时b必须为false。
这只是一个适用于布尔类型等两个值类型的原子操作示例。
原子操作不仅仅涉及到破碎的值,所以虽然我同意您和其他帖子发布者的观点,即我不知道 torn bool
可能存在的环境,但问题的关键在于更多方面。
Herb Sutter 在网上做了一次很棒的演讲,你可以观看它。请注意,这是一个漫长而又复杂的讲话。 Herb Sutter, 原子武器。问题归结为避免数据竞争,因为它使您具有顺序一致性的幻象。
某些类型的原子性完全依赖于底层硬件。每个处理器架构都对某些操作的原子性有不同的保证。例如:
Intel486处理器(以及更高版本的处理器)保证始终会原子地执行以下基本内存操作:
- 读取或写入一个字节
- 读取或写入一个按16位边界对齐的字
- 读取或写入一个按32位边界对齐的双字
其他体系结构对哪些操作是原子操作具有不同的规定。
C++是一种高级编程语言,旨在使您抽象出底层硬件。因此,标准不能允许您依赖这些低级别的假设,否则您的应用程序将不可移植。因此,C ++11兼容的标准库通过提供 atomic
的替代方案为C++中的所有基本类型提供了开箱即用的支持。
atomic
有点包含了volatile
的这个属性,所以 while(!var){}
不会被优化成 if(!var) infinite_loop();
。详见MCU programming - C++ O2 optimization breaks while loop。 - Peter Cordes
atomic<bool>
。当两个线程访问同一内存位置时,并且其中至少一个是写操作时,就会发生竞争条件。如果您的程序包含竞争条件,则行为未定义。 - nosidint
值进行逐个字节或字操作一样。因此,如果写入已经是原子性的,就不应该存在任何竞争条件。 - Robert Harvey