假设我们有两个线程,一个在循环中读取bool值,另一个可以在特定时间切换它。我个人认为这应该是原子性的,因为在C++中
所以是还是否?
编辑:
未来参考,对于
sizeof(bool)
是1字节,而且您不会部分读/写字节,但我想要100%确定。所以是还是否?
编辑:
未来参考,对于
int
是否适用相同的情况?"原子"类型在C++11中解决了三个独立的问题:
撕裂:读或写涉及多个总线周期,并且在线程操作过程中发生了切换。这可能会产生不正确的值。
缓存一致性:来自一个线程的写入更新其处理器高速缓存,但不更新全局内存;另一个线程的读取从全局内存读取,并且无法看到其他处理器高速缓存中的更新值。
编译器优化:编译器根据假设对读取和写入的顺序进行重排,认为不会从另一个线程访问值,导致混乱。
使用std::atomic<bool>
确保正确管理这三个问题。不使用std::atomic<bool>
会导致代码不可移植。
memory_order_relaxed
,如果不是默认的seq_cst
,则使用atomic<bool>
。但是你的一些推理并不成立。第二点非常误导,因为没有真正的CPU是这样的。 - Peter Cordesstd::atomic<T>
的一般情况,而不仅仅是像问题所问的那样T=bool。在某些ISA上,编译器可能有理由将变量存储为两个部分(在GNU C和GNU C++中,64位计算机上哪些类型是自然原子的?-意思是它们具有原子读取和原子写入有一个AArch64的例子,使用stp
来存储一个两半相同的常量)。 - undefined一切取决于你对“原子”这个词的实际理解。
你是指“最终值将一次性更新”(是的,在x86上,字节值肯定保证这样 - 至少可以正确对齐64位的任何值),还是“如果我将此设置为true(或false),在我设置后,没有其他线程会读取不同的值”(这并不像那么确定 - 你需要一个“锁”前缀来保证这一点)。
b=false;
并不保证所有其他线程在下一次使用 if (b) ...
时都能发现 b
是 false。这要求编译器没有把对 b
的访问优化为 tmp=b; ... if(tmp) ...
(其中 tmp
是一个寄存器)。根据线程内部代码的不同,有些情况下编译器确实会这样做。 - Mats Peterssonmfence
或lock
前缀仅需要用于确定“之后”的含义。在所有x86系统上,内存是一致的,因此在存储指令最终提交到L1d缓存后,没有其他线程可以读取旧值。您只需要屏障来实现seq-cst存储,并确保在全局可见之前,此线程不进行任何其他加载。它肯定很快就会变得全局可见。[我可以强制多核x86 CPU上的高速缓存一致性吗?] (//stackoverflow.com/a/558888) - Peter Cordesx86只保证按字大小进行字对齐的读写操作。除非明确声明原子性,否则不保证任何其他操作。当然,你还需要说服编译器首先发出相关的读写操作。
atomic<T>
的一部分作用是防止编译器将值保存在寄存器中而不是内存中,因为寄存器是线程私有的。但是内存始终是一致的。只有在您想要在加载和存储之间进行排序时才需要屏障,例如使当前线程等待直到存储可见后再执行后续读取。使存储全局可见始终尽可能快地发生,而不受屏障的影响。(从存储缓冲区提交到L1d) - Peter Cordesstd::atomic<>
只需确保纯负载/纯存储的值自然对齐,并且不会被优化掉。 - Peter Cordes
sizeof(bool)
的大小。 - Lightness Races in Orbit