Rust静态内存并发裸指针操作:是否为UB及风险问询
关于Rust裸指针并发访问静态变量的未定义行为分析
问题背景
我编写了一段Rust代码,通过原始可变指针让两个线程访问并修改同一个静态usize变量。对该值的操作为非原子位操作,但逻辑上通过条件检查确保同一时间仅有一个线程写入内存地址。
代码实现
use std::time::Duration; #[derive(Clone, Copy)] struct StaticNumber(*mut usize); unsafe impl Send for StaticNumber {} fn main() { let number = StaticNumber(Box::leak(Box::new(usize::MAX)) as *mut usize); let t1 = std::thread::spawn(move || thread1(number)); let t2 = std::thread::spawn(move || thread2(number)); let _ = t1.join(); let _ = t2.join(); } fn thread1(number: StaticNumber) { loop { if unsafe { *number.0 } & 1 == 0 { unsafe { *number.0 |= 1 }; } else { std::thread::sleep(Duration::from_millis(1)); } } } fn thread2(number: StaticNumber) { loop { if unsafe { *number.0 } & 1 == 1 { unsafe { *number.0 &= usize::MAX << 1 }; } else { std::thread::sleep(Duration::from_millis(1)); } } }
核心逻辑
- 每个线程读取
usize值的最低有效位(LSB); - 根据LSB,线程1将其设为1,线程2将其重置为0;
- 逻辑上通过修改前的LSB检查,确保同一时间只有一个线程写入内存。
但这段代码使用裸指针并发访问同一内存,通常被认为会触发Rust的未定义行为(UB),潜在问题包括操作非原子性、编译器/CPU优化干扰、违反Rust别名规则。现提出三个技术问题:
- 根据Rust内存模型,该代码是否确实属于未定义行为?
- 即便存在条件检查,非原子位操作是否仍会引发内存损坏、崩溃等不良行为?
- 即便程序看似保证了更新的互斥性,编译器或CPU优化(如指令重排、过期读取、推测执行)是否会破坏该逻辑?
我知晓使用原子操作是正确的处理方式,但好奇该带有最终一致性模型的特定代码在技术层面是否真的属于UB,希望结合Rust别名规则、编译优化及底层硬件行为进行解释。
问题解答
1. 是否属于Rust内存模型下的未定义行为?
是,肯定属于未定义行为,核心原因有两点:
- 违反Rust别名规则:Rust的内存安全核心规则是,同一时间内内存的访问只能是“单一可变访问”或“多个不可变访问”,二者不可共存。这段代码中两个线程同时持有指向同一内存的裸指针,且都执行了读+写操作,等同于同时存在多个可变访问,直接突破了Rust的内存安全边界。
- 未同步的并发读写:Rust内存模型基于C++11标准,其中明确规定未同步的并发读写(写操作与读/写操作无同步机制协调)属于未定义行为。你的代码没有使用任何同步原语(互斥锁、原子操作的内存屏障等),无论逻辑上的条件检查如何,这种访问模式在内存模型层面都是非法的。
2. 非原子位操作是否会引发内存损坏或崩溃?
完全可能。非原子的读-改-写操作(如*number.0 |= 1)会被硬件拆分为三个独立步骤:读取内存值到寄存器、修改寄存器值、写回内存。在并发场景下,这些步骤可能和另一个线程的操作交错执行:
比如线程1读取LSB为0后,线程2也同时读取到LSB为0(此时线程1还未完成写回),随后两个线程都执行写操作,最终内存中的值会出现逻辑上的矛盾状态。这种状态错乱可能导致后续逻辑异常,极端情况下会触发崩溃(比如依赖该值特定位的代码执行非法内存访问)。
3. 编译器/CPU优化是否会破坏互斥逻辑?
绝对会,且破坏方式不可预测:
- 编译器指令重排:LLVM编译器在无同步约束时会优化指令顺序,比如把线程1中的“写LSB”操作提前,或者将读操作缓存到寄存器,导致线程1永远看不到线程2的修改,陷入死循环或错误的执行分支。
- CPU缓存不一致:多核心CPU中每个线程可能持有内存的独立缓存副本,无同步机制时,一个核心的写操作不会及时同步到其他核心的缓存,导致其他线程读取到过期值——比如线程2已重置LSB为0,但线程1的缓存中仍保留旧值,持续执行错误逻辑。
- CPU推测执行:CPU可能提前执行条件分支中的写操作,即便后续条件检查不成立,这会直接导致内存被错误修改,破坏原本的互斥逻辑。
这些优化完全符合硬件和编译器规范,因为代码没有提供任何内存屏障或同步原语,无法告知编译器/CPU“该内存访问需要跨线程同步”。
内容的提问来源于stack exchange,提问作者Babur Makhmudov
相关产品推荐
相关产品推荐

