是什么促使C++编译器执行线程同步操作?
为什么C++编译器不会对原子变量做不当优化?
这是一种经典的多线程算法——无需在意其实用性:
std::atomic<bool> turn; std::atomic<int> counter; void thread(bool thread_id) { for(;;) { if(turn.load(std::memory_order::acquire) == thread_id) { counter += 1; turn.store(!thread_id, std::memory_order::release); } else { printf("spinning"); } } } // 并发运行thread(true)和thread(false)
编译器可对原子变量进行优化,比如合并连续加载存储、调整操作至load-acquire后或store-release前。那么是什么阻止编译器将原代码转换为如下形式呢?
void thread(bool thread_id) { bool turn_ = turn.load(std::memory_order::acquire); for(;;) { if(turn_ == thread_id) { counter += 1; turn_ = !thread_id; } else { printf("spinning"); } } turn.store(turn_, std::memory_order::release); }
甚至是这种形式?
void thread(bool thread_id) { if(turn.load(std::memory_order::acquire) == thread_id) { counter += 1; } for(;;) { printf("spinning"); } }
核心原因是C++内存模型对原子操作的约束,以及编译器必须遵守的as-if规则在多线程场景下的扩展:
原子操作的可见性与顺序性约束
对于std::memory_order::acquire加载操作:编译器不能将后续内存操作重排到该load之前,且必须读取其他线程通过release操作写入的最新值,不能缓存旧值重复使用(比如不能把循环内的load提到循环外)。
对于std::memory_order::release存储操作:编译器不能将之前的内存操作重排到该store之后,且必须把修改同步到其他线程可见的内存中,不能延迟到函数结束才执行。多线程场景下的as-if规则
编译器优化必须保证程序的可观察行为与原代码一致——在多线程环境中,可观察行为包括其他线程能看到的内存操作顺序和值。你给出的两种优化方式会让线程永远看不到另一个线程对turn的修改,彻底破坏原算法逻辑,违反as-if规则。原子变量的特殊属性
C++标准明确规定,原子操作的执行必须符合指定的内存顺序语义,编译器不能随意合并、移动或省略原子操作,除非能证明修改不会影响任何线程的可观察行为——显然这两种优化都严重破坏正确性,因此编译器绝对不会这么做。
内容的提问来源于stack exchange,提问作者Stack Exchange Broke The Law
相关产品推荐
相关产品推荐

