从C++标准视角解析:RMW操作为何比纯Load不丢失唤醒信号?
正确的RMW实现示例
#include <iostream> #include <thread> #include <atomic> extern void block_wait(); extern void wake(); int main(){ std::atomic<int> counter = 0; std::jthread t1([&](){ if(counter.fetch_sub(1,std::memory_order::relaxed) == 0){ // #0 block_wait(); // #1 } }); std::jthread t2([&](){ if(counter.fetch_add(1,std::memory_order::relaxed) == -1){ // #2 wake(); // #3 } }); }
在此示例中,block_wait和wake不会引入数据竞争,功能如其名称所示:block_wait会阻塞线程并等待唤醒信号以解除阻塞,wake则会唤醒被阻塞的线程。
若#0读取到值0,则#2必然会读取到值-1并执行wake()来唤醒#1。
错误的纯Load实现示例
#include <iostream> #include <thread> #include <atomic> extern void block_wait(); extern void wake(); int main(){ std::atomic<int> counter = 0; std::jthread t1([&](){ if(counter.fetch_sub(1,std::memory_order::relaxed) == 0){ // #0 block_wait(); // #1 } }); std::jthread t2([&](){ auto r = counter.load(std::memory_order::relaxed); // #2 if(r == -1){ wake(); // #3 } counter.store(r+1,std::memory_order::relaxed); // #4 }); }
在#0读取到0并执行#1阻塞线程的前提下,#2可能读取到0而不执行#3,无法保证唤醒被阻塞的线程,算法正确性无法保障。
三种解释的正确性判断
注:所有讨论均基于#0读取初始值0的前提
解释1:RMW操作的Load部分相比纯Load,保证不会读取过期值(stale value)
错误。C++标准中并没有“过期值”的明确定义,标准是通过修改顺序、可见性和一致性规则来约束原子操作的行为,而非“过期值”这种模糊概念。无论是RMW的Load还是纯Load,只要符合标准的一致性规则,读取到的值都是合法的;RMW的特殊性并非在于避免“过期值”,而是其Load和Store是原子绑定的。
解释2:RMW操作的Load部分相比纯Load,保证读取修改顺序中更晚的修改
错误。修改顺序是原子对象所有修改操作的全序关系,纯Load可以读取修改顺序中任意在它之前完成的修改(符合一致性规则的前提下),RMW的Load部分同样需要遵循修改顺序的约束,并不存在“保证读取更晚修改”的规则。二者的核心差异在于RMW是原子的读-改-写操作,其Load和后续的Store在修改顺序中是连续的,而纯Load和独立的Store之间没有这种原子绑定关系。
解释3:C++标准对RMW操作的Load部分施加了比纯Load更严格的限制
正确。除了[intro.races]中定义的通用内存一致性规则外,[atomics.order] p10明确规定:对于原子对象M的RMW操作,其Load部分必须读取M的修改顺序中在该RMW的Store部分之前的某个修改的值,并且整个RMW操作在M的修改顺序中表现为一个单一的修改。而纯Load仅需满足读取的值是M的修改顺序中某个在该Load之前完成的修改的值,没有绑定后续操作的约束。在本场景中,#0的fetch_sub(RMW)会将counter的修改顺序更新为0→-1,#2的fetch_add(RMW)必须读取修改顺序中在其Store之前的值,也就是-1;而纯Load的#2可以读取初始值0(只要符合一致性规则),因为它和后续的store是独立操作,没有原子绑定的要求。
补充说明:wake执行时会生成一个令牌,可唤醒之前执行block_wait的线程(若存在),或被后续block_wait()消耗(即调用立即返回);其行为类似Rust的park/unpark,而非condition_variable。
对于#2读取0且#4的修改不紧跟#2读取的修改这类情况,算法会出现错误执行,但本次讨论仅聚焦于#0读取0的场景。
内容的提问来源于stack exchange,提问作者xmh0511

