为何C++编译器允许优化掉基于std::atomic的忙等待循环?
问题代码示例
#include <iostream> #include <thread> #include <atomic> #include <chrono> int main() { std::atomic<bool> ready = false; std::thread threadB = std::thread([&]() { while (!ready) {} printf("Hello from B\n"); }); std::this_thread::sleep_for(std::chrono::seconds(1)); printf("Hello from A\n"); ready = true; threadB.join(); printf("Hello again from A\n"); }
编译器允许此类优化的核心原因
这个问题的本质是C++标准的前向进展规则和原子操作语义的共同作用,不是std::atomic本身失效了:
- 首先纠正常见误区:
std::atomic的核心作用是保证操作的原子性、禁止违规的指令重排序、符合指定内存序的可见性要求,但它不会突破C++标准对程序执行流程的通用约束,也不意味着编译器完全不能对原子变量的访问做任何优化。 - C++标准明确赋予了编译器优化无意义循环的权限:标准规定编译器可以默认假设任何线程最终都会终止,或者执行I/O、访问
volatile变量、执行同步操作(包括符合内存序要求的原子操作),不会永久卡在没有任何实际进展的空循环里。 - 示例中的
while (!ready) {}是空循环,除了读取ready之外没有任何其他操作。编译器此时可以做出如下合理假设:如果这个循环能退出,说明ready的值会在循环执行过程中被修改,但当前线程本身不会修改ready,如果没有其他同步操作约束,编译器可以把ready的读取操作提升到循环之外,只读取一次值存入寄存器,如果第一次读取到false,就直接变成无限死循环,不需要再反复从内存读取ready的值。这种优化完全符合标准要求,因为标准没有要求编译器必须保证无限空循环里的原子操作能实时看到其他线程的修改,这种无意义的无限忙等本身就属于未定义行为的范畴。 - 要避免这个问题也很简单,只需要在忙等循环里加一点有实际意义的操作,比如调用
std::this_thread::yield()让出CPU时间片,或者加一个短时间的sleep,此时循环就有了明确的同步/阻塞操作,编译器就不会再做上述优化。
内容的提问来源于stack exchange,提问作者Ashot
相关产品推荐
相关产品推荐

