线程间响应原子自增变更:memory_order选择是否正确?
问题分析与解答
代码示例
#include <atomic> #include <thread> bool read_from_network() noexcept; void write_to_network() noexcept; int main() { std::atomic<size_t> work_counter; std::thread t1([&work_counter] { while (true) { if (read_from_network()) work_counter.fetch_add(1, std::memory_order_release); } }); std::thread t2([&work_counter] { const auto prev_val = work_counter.load(std::memory_order_acquire); write_to_network(); const auto new_val = work_counter.load(std::memory_order_acquire); if (new_val != prev_val) { // get reply **after** write_to_network } }); t1.join(); t2.join(); }
1. 当前选择的std::memory_order是否正确?
不正确。原因如下:
- 重排风险:
t2中的write_to_network()是普通非原子操作,当前内存序没有约束它与后续new_val.load()的顺序关系。编译器或CPU可能将write_to_network()重排到new_val.load()之后,导致即使new_val检测到计数器变化,对应的t1网络读取操作也可能发生在write_to_network()之前,不符合需求(注:用户说明忽略prev_val加载后、write前的情况,但重排会导致write被延后,误判更早的网络事件为write后的)。 - 同步方向不匹配:当前的
release-acquire仅保证t1中fetch_add之前的操作(触发计数器增量的read_from_network())happens-beforet2中new_val.load()之后的代码,但无法建立t2的write_to_network()happens-beforet1对应read_from_network()的关系,无法确保计数器增量是write后的网络事件导致的。
2. 能否使用memory_order_relaxed保证程序正常工作,还是必须用memory_order_seq_cst?
- 不能使用memory_order_relaxed:relaxed操作没有任何同步或顺序约束,编译器和CPU可以自由重排原子操作与普通操作,完全无法保证
write_to_network()与计数器加载的顺序,必然会出现误判。 - 需要使用memory_order_seq_cst:seq_cst操作会建立全局总顺序,同时普通操作不会跨越seq_cst操作重排。修改方式为:
- 将
t1中fetch_add的内存序改为std::memory_order_seq_cst - 将
t2中两次load的内存序改为std::memory_order_seq_cst
- 将
这样可以保证:
t2中的write_to_network()会被约束在两次seq_cst加载之间,不会被重排到new_val.load()之后;- 全局总顺序会确保:如果
new_val检测到计数器增量,那么对应的t1的fetch_add操作必然发生在t2的write_to_network()之后,满足“仅处理write后网络事件”的需求。
内容的提问来源于stack exchange,提问作者user1244932
相关产品推荐
相关产品推荐

