原子操作内存序:relaxed能否避免函数重复调用,是否需Acquire/Release?
问题描述
为避免need_close()函数被重复调用,是否需要通过Acquire/Release内存序建立同步关系?
示例代码
#include <iostream> #include <atomic> #include <random> #include <thread> int need_close(){ random_device rd; std::mt19937 gen(rd()); uniform_int_distribution<int> distribute(0, 2); return distribute(gen); } void invoke(std::atomic<bool>& is_close){ if(is_close.load(std::memory_order::relaxed)){ // #1 return; } auto r = need_close(); if(r==0){ is_close.store(true,std::memory_order::relaxed); } } int main(){ std::atomic<bool> is_close{false}; std::thread t1([&](){ for(auto i = 0; i<100000;i++) invoke(is_close); }); std::thread t2([&](){ for(auto i = 0; i<100000;i++) invoke(is_close); }); t1.join(); t2.join(); }
当前代码使用relaxed内存序操作原子变量is_close,预期逻辑是:当某一线程将is_close设为true后,其他线程读取到该值就不再调用need_close()。但存在疑问:relaxed内存序是否会导致编译器/CPU指令重排,把#1处的load操作移到need_close()调用之后,进而导致函数被不必要地重复调用?
结论与解释
必须使用Acquire/Release内存序来约束指令重排,才能保证预期逻辑生效。
为什么relaxed内存序不可行?
relaxed内存序仅保证原子操作本身的原子性,不提供任何跨线程的同步约束,甚至允许同一线程内的指令重排——只要重排不违反单线程的程序语义。
具体到这段代码,编译器完全可能将need_close()的调用重排到is_close.load()之前执行:单线程视角下,不管先执行判断还是先调用函数,最终的控制流结果看起来一致(比如如果load返回true就直接return,但重排后先调用函数再判断,单线程内不会出现逻辑矛盾),这种重排是符合relaxed内存序规则的。但在多线程场景下,这会导致即使is_close已经被其他线程设为true,当前线程仍可能先执行need_close(),违背了预期逻辑。
正确的内存序用法
给is_close的加载和存储操作分别指定acquire和release内存序:
release存储:保证当前线程中所有在store之前的操作,不会被重排到store之后。acquire加载:保证当前线程中所有在load之后的操作,不会被重排到load之前。
修改后的invoke函数代码:
void invoke(std::atomic<bool>& is_close){ if(is_close.load(std::memory_order::acquire)){ // Acquire内存序 return; } auto r = need_close(); if(r==0){ is_close.store(true,std::memory_order::release); // Release内存序 } }
这样就能确保#1处的load操作一定在need_close()调用之前执行,当读取到is_close == true时,会直接return,不会触发不必要的函数调用。
内容的提问来源于stack exchange,提问作者xmh0511

