如何在真实编译器与CPU上复现relaxed内存序下的LoadStore重排?
#include <atomic> #include <thread> void test_relaxed() { using namespace std; atomic<int> x{0}; atomic<int> y{0}; std::thread t1([&] { auto r1 = y.load(memory_order_relaxed); //a x.store(r1, memory_order_relaxed); //b }); std::thread t2([&] { auto r2 = x.load(memory_order_relaxed); //c y.store(42, memory_order_relaxed); //d }); t1.join(); t2.join(); }
根据C++参考文档的relaxed ordering示例,上述代码允许出现r1 == r2 == 42的结果,但在x86-64和arm64平台测试后始终无法复现该现象,如何在真实编译器与CPU环境中重现这一结果?
复现r1 == r2 == 42的具体方法
要触发这个结果,核心是让指令执行顺序刚好满足d → a → b → c(线程2先执行y.store(42),线程1加载到y的42并存入x,最后线程2加载x的42),以下是可行手段:
- 大循环迭代测试
单次测试出现目标结果的概率极低,必须将测试逻辑包裹在百万级以上的循环中,统计触发次数:
#include <atomic> #include <thread> #include <iostream> int main() { int hit_count = 0; const int total_iter = 1000000; for (int i = 0; i < total_iter; ++i) { std::atomic<int> x{0}; std::atomic<int> y{0}; int r1 = 0, r2 = 0; std::thread t1([&] { r1 = y.load(std::memory_order_relaxed); x.store(r1, std::memory_order_relaxed); }); std::thread t2([&] { r2 = x.load(std::memory_order_relaxed); y.store(42, std::memory_order_relaxed); }); t1.join(); t2.join(); if (r1 == 42 && r2 == 42) { hit_count++; } } std::cout << "触发r1==r2==42的次数: " << hit_count << "/" << total_iter << "\n"; return 0; }
- 插入指令延迟干扰
在关键内存操作前后插入空操作,强制CPU调度产生延迟,增加内存可见性延迟或指令重排的概率:
// 线程1(GCC/clang语法) std::thread t1([&] { // 插入空操作延迟,让线程2优先执行store for (int k = 0; k < 100; ++k) { __asm__ volatile("nop"); } r1 = y.load(std::memory_order_relaxed); x.store(r1, std::memory_order_relaxed); }); // 线程2 std::thread t2([&] { r2 = x.load(std::memory_order_relaxed); // 延迟让线程1的load看到store结果 for (int k = 0; k < 100; ++k) { __asm__ volatile("nop"); } y.store(42, std::memory_order_relaxed); });
MSVC可替换为__nop()指令。
- 绑定线程到不同CPU核心
将两个线程绑定到不同物理核心,利用跨核内存可见性的延迟提升触发概率(Linux示例):
#include <pthread.h> void bind_to_cpu(int cpu_id) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(cpu_id, &cpuset); pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); } // 线程函数开头添加绑定逻辑 std::thread t1([&] { bind_to_cpu(0); r1 = y.load(std::memory_order_relaxed); x.store(r1, std::memory_order_relaxed); }); std::thread t2([&] { bind_to_cpu(1); r2 = x.load(std::memory_order_relaxed); y.store(42, std::memory_order_relaxed); });
- 切换到弱内存模型平台
x86-64是TSO(全存储顺序)内存模型,对store-load操作有默认顺序保证;arm64的内存模型也比ARMv7更强。优先选择ARMv7或更早的ARM架构平台测试,这类弱内存模型更容易触发relaxed序的极端情况。
为什么x86/arm64难复现?
x86-64的TSO模型保证所有store操作按全局顺序执行,线程的store对其他线程的load有一定可见性顺序;arm64虽然支持relaxed内存序,但相比旧ARM架构增强了内存一致性,所以这类极端指令重排的出现概率极低,必须通过大量迭代或人为干扰才能触发。
内容的提问来源于stack exchange,提问作者Jichao
相关产品推荐
相关产品推荐

