原子变量紧凑无限循环:编译器能否提升检查?相关技术疑问
先看示例代码:
原始代码:
std::atomic<bool> ready = false; void run() { while(!ready){} // ... 执行后续操作 }
编译器是否有权将其重排为以下形式?
std::atomic<bool> ready = false; void run() { bool is_ready = ready; while(!is_ready){} // ... 执行后续操作 }
针对这个违反直觉的优化问题,我们逐一解答:
1. 编译器是否有权进行这种重排?
是的,特定条件下编译器确实可以这么做。根据C++的as-if规则,只要优化后的程序与原程序的可观察行为一致,编译器就允许进行各类优化。对于这个空循环来说,如果编译器无法证明其他线程会修改ready(比如无法追踪到跨线程的写操作),它会判定循环内的重复读取是无意义的——当前线程没有修改ready,重复读取不会产生不同结果,因此可以把读取操作提升到循环外。
即便ready是std::atomic类型,默认的memory_order_seq_cst内存顺序也不禁止这种优化。内存顺序主要约束的是跨线程操作的可见性和指令顺序,而非强制编译器每次都从内存读取。只有当编译器能确定存在其他线程的修改时,才会放弃这种提升。
2. 改为调用ready.load()能否阻止重排?
不能。直接读取std::atomic变量本质上就是调用它的operator bool(),而这个操作内部就是调用默认内存顺序的load()。显式调用ready.load()和直接读取变量语义完全等价,编译器仍然会执行相同的优化。
如果想要强制编译器每次都从内存读取ready,你需要让编译器意识到每次读取可能得到不同值——比如在循环内插入编译器内存屏障(如GCC的__asm__ __volatile__("" : : : "memory")),或者谨慎使用volatile修饰std::atomic(注意:标准未明确规定volatile与std::atomic组合的行为,不同编译器可能有差异,不推荐作为通用解决方案)。
3. 为什么CAS操作不会被类似重排?
CAS(比如std::atomic::compare_exchange_weak)是读-改-写操作,本身带有明确的副作用:每次调用都会尝试修改原子变量的值,并且返回操作是否成功。编译器无法将CAS提升到循环外,原因在于:
- CAS的返回值依赖于当前内存中的实际值,每次调用的结果可能不同;
- CAS操作会直接与内存交互,成功时会修改内存中的值,这属于可观察的副作用,编译器不能随意消除或重排这类操作。
因此,循环中的CAS操作会被编译器保留每次调用,不会被提升到循环外。
内容的提问来源于stack exchange,提问作者DDG

