You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中SeqLock实现的未定义行为(UB)及可行性探讨

SeqLock实现的技术疑问与解答
use std::alloc::Layout;
use std::sync::atomic::{fence, AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread::yield_now;

const LENGTH: usize = size_of::<usize>();

#[derive(Clone, Copy)]
struct SharedBuffer(*mut u8);
unsafe impl Sync for SharedBuffer {}
unsafe impl Send for SharedBuffer {}

fn main() {
    // for the sake of brevity allocate 8 byte array, but
    // this can be generic over any allocation size
    let alloc = unsafe { std::alloc::alloc(Layout::from_size_align(LENGTH, 1).unwrap()) };
    let store = SharedBuffer(alloc);

    let sequence1 = Arc::new(AtomicUsize::new(0));
    let sequence2 = sequence1.clone();

    std::thread::spawn(move || writer(sequence1, store));
    let _ = std::thread::spawn(move || reader(sequence2, store)).join();
}

fn writer(sequence: Arc<AtomicUsize>, store: SharedBuffer) {
    loop {
        // signal other thread that we are about to write
        let n = sequence.fetch_add(1, Ordering::AcqRel).to_le_bytes();
        unsafe { store.0.copy_from_nonoverlapping(n.as_ptr(), LENGTH) };
        // signal the thread that we are done writing
        sequence.fetch_add(1, Ordering::Release);
        yield_now();
    }
}

fn reader(sequence: Arc<AtomicUsize>, store: SharedBuffer) {
    let mut buffer = [0; LENGTH];
    loop {
        loop {
            // check whether writer thread is accessing the memory
            let before = sequence.load(Ordering::Acquire);
            if before % 2 != 0 {
                // retry if write is in progress
                yield_now();
                continue;
            }
            let ptr = buffer.as_mut_ptr();
            unsafe { ptr.copy_from_nonoverlapping(store.0, LENGTH) };
            fence(Ordering::Release);
            let after = sequence.load(Ordering::Acquire);
            // if writer thread managed to modify the data while
            // we were reading it, discard the data and retry
            if after != before {
                continue;
            }
            assert_eq!(usize::from_le_bytes(buffer), after - 2);
            break;
        }
        yield_now();
    }
}

这段代码尝试用类SeqLock机制在读写线程间共享内存,设计思路是:明知存在会引发**未定义行为(UB)**的数据竞争,但试图通过检测竞争并重试读取,实现读线程无阻塞的乐观读取。当前代码能在多个主流平台和编译器版本下编译运行,但UB的存在意味着程序行为不可预测,当前正常运行不代表在所有环境下都能工作,本质存在安全问题。


技术疑问解答

1. 是否意味着在Rust、C++这类语言中,无法实现针对任意大内存区域的SeqLock?

不是无法实现,但必须严格遵循语言的内存模型,不能直接放任数据竞争。

传统SeqLock的核心是允许读操作与写操作并发,通过序列计数器验证读取的完整性,但Rust/C++的内存模型中,对非原子共享内存的并发读写会直接触发数据竞争导致的UB——即使后续检测到并重试,读取撕裂数据的动作本身已经违反了语言规范,可能导致编译器优化出逻辑错误、程序崩溃或其他不可预测的结果。

要合法实现任意大内存的SeqLock,需要:

  • 用原子变量维护序列计数器,确保其操作的原子性和内存顺序;
  • 配合内存屏障(fence)阻止编译器和硬件的指令重排,保证读写操作的可见性和顺序性;
  • 在unsafe代码中严格控制共享数据的访问,确保当序列值验证通过时,读取到的数据是完整一致的(这依赖目标硬件的内存模型特性,比如x86的强有序性、ARM需要额外的屏障指令);
  • 一旦检测到序列值变化,立即丢弃无效数据并重试,绝不使用可能撕裂的脏数据。

示例代码的问题在于,它直接对非原子共享内存进行并发读写,没有采取足够的措施避免UB,本质是依赖编译器和硬件的非标准行为,而非合法的语言特性。

2. 若针对特定架构使用汇编编写该算法,是否能保证其正确性?

针对特定架构用汇编编写SeqLock,可以保证在该架构下的正确性,但不具备跨架构的可移植性。

原因在于:

  • 汇编可以直接控制内存访问的顺序,插入符合目标架构要求的内存屏障指令(比如x86的mfence、ARM的dmb),完全遵循架构的内存模型规范;
  • 可以直接指定内存访问的方式,避免编译器优化导致的指令重排或数据撕裂;
  • 但不同架构的内存模型差异极大(比如x86是强有序架构,ARM是弱有序架构),针对某一架构编写的汇编SeqLock在其他架构上可能完全失效,甚至触发硬件层面的数据不一致问题。

即使是汇编实现,也需要严格遵循目标架构的内存模型规则,否则仍然会出现逻辑错误。


内容的提问来源于stack exchange,提问作者Babur Makhmudov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 16:58:10