C++内存序疑问:共享资源线程安全访问的内存序选择及测试工具
一、std::memory_order_relaxed内存序的安全性分析
你的代码在x86平台运行正常是因为x86默认采用强内存模型(禁止读写、写读重排),但在ARM、PowerPC等弱内存模型平台上,使用relaxed内存序会存在数据竞争风险,核心原因是缺少必要的happens-before同步关系:
存在的关键问题
控制器线程操作重排:
控制器中stop.store(true)(#1)和resource = false(#3)使用relaxed时,弱内存模型的CPU/编译器可能重排这两个操作,导致resource被提前销毁,而工作线程还未看到stop=true的信号。工作线程操作重排:
工作线程中working.fetch_add(1)(#4)和stop.load()(#5)是独立的relaxed原子操作,编译器/CPU可能交换它们的执行顺序。比如工作线程先读取stop=true并返回,但还未执行fetch_add(1),此时控制器看到working=0就销毁资源,后续工作线程执行fetch_add(1)后会访问已销毁的resource,触发错误。活跃线程计数同步缺失:
控制器循环等待working=0时,working.load()(#2)使用relaxed无法保证所有工作线程的working.fetch_sub(1)(#8)操作已完成,也无法保证工作线程的resource访问操作已结束。弱内存模型下,控制器可能提前执行resource=false,与工作线程的resource访问形成数据竞争。
修正后的内存序方案
需要通过release-acquire内存序建立正确的同步关系:
#include <atomic> #include <iostream> #include <thread> #include <vector> int main(int, char **) { volatile bool resource = true; std::atomic_bool stop{false}; std::atomic_int working{0}; std::thread controller([&]() { // 发布stop信号,确保后续销毁操作不会提前执行 stop.store(true, std::memory_order_release); // #1 // 等待所有活跃线程完成,acquire确保看到所有fetch_sub的完成 while (working.load(std::memory_order_acquire)) { // #2 std::this_thread::yield(); } // 销毁资源 resource = false; // #3 }); std::vector<std::thread> workers; for (int i = 0; i < 64; ++i) { workers.emplace_back([&]() { working.fetch_add(1, std::memory_order_relaxed); // #4:计数仅需relaxed // 获取stop信号,acquire确保如果看到true,控制器的store已完成 if (stop.load(std::memory_order_acquire)) { // #5 working.fetch_sub(1, std::memory_order_release); // #6:release同步计数减少 return; } // 访问资源:单线程顺序保证此操作在fetch_sub之前 if (!resource) { // #7 std::cerr << "Data race detected: resource is not available." << std::endl; abort(); } // release同步:确保资源访问已完成,控制器能看到计数减少 working.fetch_sub(1, std::memory_order_release); // #8 }); } for (auto &worker : workers) worker.join(); controller.join(); std::cout << "no data race detected." << std::endl; return 0; }
关键调整说明:
- #1 使用
release:禁止后续操作(#3)重排到stop.store之前,确保工作线程看到stop=true时,控制器还未销毁资源。 - #5 使用
acquire:与#1的release形成同步,确保工作线程看到stop=true后,不会再访问资源;若看到stop=false,则资源访问操作发生在控制器销毁之前。 - #2 使用
acquire:与#6/#8的release形成同步,确保控制器看到working=0时,所有工作线程的资源访问已完成。 - #6/#8 使用
release:确保工作线程的资源访问操作(若有)happens-before计数减少,控制器能正确感知线程已退出。
二、跨平台测试内存重排的工具
以下是常用的工具和方法:
- ThreadSanitizer (TSan):GCC、Clang、MSVC均支持,可检测数据竞争和内存序问题。通过设置环境变量
TSAN_OPTIONS=memory_model=weak可模拟弱内存模型的行为,帮助发现强内存模型下隐藏的问题。 - Relacy Race Detector:专门针对C++并发代码的工具,支持模拟多种内存模型(x86、ARM、PowerPC等),能自动检测内存重排、数据竞争等并发问题。
- CDSChecker:微软开发的工具,用于验证C++并发数据结构的正确性,支持弱内存模型的模拟和检测。
- 编译器汇编分析:生成不同平台(如ARM、PowerPC)的汇编代码,手动检查原子操作的内存屏障指令是否正确,判断内存序是否符合预期。
内容的提问来源于stack exchange,提问作者Monte

