如何测试检测Rust中Relaxed内存序的错误用法?
我写了一段Tokio测试代码,理论上在弱内存序机器上会出现不稳定,但在Mac M2上始终复现不了问题。代码逻辑是线程1先往并发队列push数据,再用Relaxed内存序设置ready标志;线程2轮询ready标志,为true时从队列pop数据并断言。由于Relaxed未建立happens-before关系,线程2可能看到ready为true但队列仍为空,但实际测试没触发这个问题。请问怎么才能检测出这种Relaxed内存序的错误用法?
测试代码如下:
#[tokio::test(flavor = "multi_thread")] async fn test_ready() { let ready = Arc::new(AtomicBool::new(false)); let concurret_vec = Arc::new(concurrent_queue::ConcurrentQueue::<usize>::bounded(1)); let concurret_vec_copy = concurret_vec.clone(); let ready_copy = ready.clone(); let handle1 = tokio::spawn(async move { concurret_vec_copy.push(1).unwrap(); ready_copy.store(true, Ordering::Relaxed); }); let concurret_vec_copy = concurret_vec.clone(); let ready_copy = ready.clone(); let handle2 = tokio::spawn(async move { while !ready_copy.load(Ordering::Relaxed) { tokio::time::sleep(Duration::from_millis(10)).await; } assert_eq!(concurret_vec_copy.pop().unwrap(), 1); }); handle1.await.unwrap(); handle2.await.unwrap(); }
可行的检测方法:
增加测试迭代次数
内存序问题是概率性的,单次测试很难触发。可以把测试逻辑放在循环里执行数千甚至数万次,提升触发概率。比如修改测试函数,在内部加一个for _ in 0..10000循环,每次循环重新创建原子变量和队列,重复执行原有的线程逻辑。使用内存模型测试库
loomloom是Rust生态专门用于模拟弱内存序行为的测试工具,它能强制触发编译器和CPU的指令重排序,从而暴露内存序错误。
示例用法:
首先在Cargo.toml添加依赖:
[dev-dependencies] loom = "0.5" tokio = { version = "1.0", features = ["full"] } concurrent-queue = "2.0"
然后改写测试代码为loom兼容的版本:
use loom::sync::atomic::{AtomicBool, Ordering}; use loom::sync::Arc; use concurrent_queue::ConcurrentQueue; #[test] fn test_ready_loom() { loom::model(|| { let ready = Arc::new(AtomicBool::new(false)); let queue = Arc::new(ConcurrentQueue::<usize>::bounded(1)); let q1 = queue.clone(); let r1 = ready.clone(); let t1 = loom::thread::spawn(move || { q1.push(1).unwrap(); r1.store(true, Ordering::Relaxed); }); let q2 = queue.clone(); let r2 = ready.clone(); let t2 = loom::thread::spawn(move || { while !r2.load(Ordering::Relaxed) { // 忙等,避免sleep影响测试 std::hint::spin_loop(); } assert_eq!(q2.pop().unwrap(), 1); }); t1.join().unwrap(); t2.join().unwrap(); }); }
运行这个测试,loom会自动遍历所有可能的线程执行顺序和内存重排序情况,一旦发现断言失败,就会报告内存序错误。
修改测试逻辑,替换sleep为忙等
原测试中的tokio::time::sleep会让线程主动让出CPU,可能掩盖内存序问题。换成忙等(配合std::hint::spin_loop()减少CPU占用),让线程更频繁地轮询ready标志,更容易触发可见性问题。在弱内存序硬件上测试
Mac M2基于ARM架构,但苹果的ARM实现对内存序的兼容性较好,部分场景下不会触发极端的重排序。可以尝试在更弱内存序的硬件(比如某些ARM服务器、PowerPC机器)上运行测试,或者用QEMU模拟这类环境,更容易暴露问题。
内容的提问来源于stack exchange,提问作者silverwen

