使用 iceoryx2 的 ByteAtomic 实现安全无锁原语
摘要
本文介绍 iceoryx2 的 ByteAtomic,这是一种按字节操作的原子包装器,可在 Rust 和 C++ 中实现序列锁等无锁原语时防止未定义行为。
暂无内容
查看缓存全文
缓存时间:
2026/08/04 13:45
# 使用 iceoryx2 的 ByteAtomic 实现安全无锁原语 来源:https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/ Marika Lehmann \- 28/07/2026 ## 数据竞争与顺序锁 (https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#data-races-and-sequence-lock) 在多线程编程中,一个常见场景是多个线程同时读取和修改共享数据。如果这些读写操作不是原子的,就会发生数据竞争。在 Rust 和 C++ 这类内存模型几乎相同的语言中,这将导致未定义行为。为防止这种情况,可以使用锁来保护数据,避免其在被读取时被修改。然而,传统的加锁机制存在死锁风险,这在安全至上的高可靠性系统中尤其不可接受。 一种无需使用阻塞锁即可缓解上述数据竞争的常见方法是利用顺序锁。顺序锁包含共享数据和一个原子计数器,只要数据正在被更新,该计数器的值就为奇数: 使用顺序锁时,写线程将序列计数器递增为奇数,更新数据,然后再将计数器递增为偶数。读线程在复制共享数据前后各读取一次序列计数器。如果计数器已发生变化或者当前为奇数,则表明数据可能被并发修改。此时读线程丢弃损坏的副本并重试。 sequence-lock **问题:** 即使读线程检测到数据已被修改并在使用前丢弃了副本,复制非原子数据这一行为本身仍然会触发未定义行为。虽然顺序锁可以*检测*到数据竞争的发生,但并不能*阻止*它。因此,目前无法在 Rust 或 C++ 中实现正确的顺序锁,除非将数据分解为更小的、各自独立的原子部分。这是一个已知问题,虽然目前已有关于向 Rust 和 C++ 标准库引入“原子 memcpy”¹ (https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#user-content-fn-1)² (https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#user-content-fn-2) 的提案正在推进,但我们还不能依赖这一特性。 iceoryx2 面向安全关键和高可靠性系统,提供了一套基于类似顺序锁机制的无锁构件库 (https://docs.rs/iceoryx2-bb-lock-free/latest/iceoryx2_bb_lock_free/)。为了使这些构件既安全又正确,我们需要一种能够在字节级别上原子执行内存复制的方法,从而确保不发生数据竞争。这就是我们实现逐字节原子包装器 `ByteAtomic` (https://github.com/eclipse-iceoryx/iceoryx2/blob/f0a1e6d03459908b41938bf5c89a1cd29f588da8/iceoryx2-bb/container/src/byte_atomic.rs) 的原因,我们将在接下来的章节中进行描述。虽然其概念很简单,但为了实现真正的安全性,我们需要克服一个与未初始化内存相关的微妙但关键的问题。 ## 解决方案:逐字节原子包装器 (https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#solution-a-byte-wise-atomic-wrapper) 为防止上述数据竞争及由此产生的未定义行为,iceoryx2 中的 `ByteAtomic` 对其内部类型提供逐字节的原子读写操作。该包装器仅保证*每个字节*都被原子地更新/读取;它并不提供更高级别的线程安全保证。用户仍然必须实施适当的同步机制(例如顺序锁),以防止撕裂读或撕裂写。该包装器仅确保内存复制不会导致未定义行为,但它本身并不保证数据完整性。 ### 实现 (https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#implementation) 随着我们解决了内存安全的各种复杂性问题,该包装器的实现也经历了一些改进。我们 `ByteAtomic` 包装器的初始版本如下所示: 它被命名为 `FixedSizeByteAtomic`,因为数组大小必须在编译时提供,因为 Rust 目前还不允许在结构体定义中直接使用 `core::mem::size_of::()`。一旦将来允许这样做,我们计划移除 `SIZE` 泛型参数,删除运行时定长版本 `RelocatableByteAtomic`,并将该结构体重命名为 `ByteAtomic`。 #### 填充字节 (https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#padding-bytes) 为了理解实现为何必须演进,让我们来看看 `new()` 最初的、朴素的实现: 这个版本的 `new()` 接受一个可复制的值,通过 `transmute_copy` 将其转换为字节数组,并将每个字节作为一个 `AtomicU8` 存储到 ByteAtomic 的 `data` 字段中。这看起来没问题——除非 `T` 包含未初始化内存,例如 `MaybeUninit` 或填充字节: `transmute_copy` (https://doc.rust-lang.org/std/mem/fn.transmute_copy.html) 假定被复制的值是目标类型的有效表示,在我们的例子中是有效的 `u8`。这个假定对于填充字节不成立,因为它们是未初始化的内存;读取它们会导致未定义行为³ (https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#user-content-fn-3)。因此,我们必须确保只复制所传值的字段(即已初始化的字节)。这引出了当前正确的 `new()` 实现: 现在我们要求内部类型 `T` 实现 iceoryx2 中针对可原子复制类型的 `AtomicCopy` (https://github.com/eclipse-iceoryx/iceoryx2/blob/f0a1e6d03459908b41938bf5c89a1cd29f588da8/iceoryx2-bb/elementary-traits/src/atomic_copy.rs) trait。它提供了 `for_each_field()`,这是一个用于逐字节复制的按字段访问器。该方法将提供的回调应用于 `T` 中每个字段的每个偏移量-大小对。这样一来,`new()` 就只将 `value` 的已初始化字节复制到 `data` 字段中,从而有效地跳过了可能的填充字节。 当然,`AtomicCopy` trait 的实现必须确保每个字段的偏移量和大小计算正确;否则仍可能发生未定义行为。 请注意,`read()` 的返回类型也发生了变化。在初始版本中,`read()` 返回 `MaybeUninit`,以提醒用户:虽然 `ByteAtomic` 防止了内存复制期间的未定义行为,但仍然可能发生撕裂读。为了强调这一风险,我们将返回类型改成了 `MaybeTorn` (https://github.com/eclipse-iceoryx/iceoryx2/blob/f0a1e6d03459908b41938bf5c89a1cd29f588da8/iceoryx2-bb/container/src/byte_atomic.rs#L105)。该类型包装了一个 `MaybeUninit`,并作为一个持续提醒:数据完整性尚不能得到保证。只有在验证没有发生并发写入之后,用户才能安全地调用 `assume_consistent()` 来提取读取的值。否则,返回的 `T` 可能逻辑上无效,使用它可能导致未定义行为。 ### 使用 (https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#usage) 为 `Foo` 手动实现 `AtomicCopy` 的代码如下: 为方便起见,我们为所有标量类型实现了 `AtomicCopy`,并提供了一个派生宏 (https://docs.rs/iceoryx2-bb-derive-macros/latest/iceoryx2_bb_derive_macros/derive.AtomicCopy.html)。该宏会自动为所有字段也都实现了 `AtomicCopy` 的结构体实现该 trait。以下是 `Foo` 的用法示例: ## 结论 (https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#conclusion) 编写正确的无锁代码是困难的。即使是“简单”且广为人知的顺序锁(它常常构成更复杂的无锁构件的基础),也伴随着数据竞争和未定义行为。虽然未来标准库中的“原子 memcpy”将是理想且高效的解决方案,但 iceoryx2 提供的逐字节原子包装器使得开发者今天就能实现正确且安全的顺序锁及其他无锁原语。我们正在努力将此包装器集成到我们现有的无锁构件中,以最终完成它们向完全安全实现的过渡。 - 在 iceoryx2 社区论坛上讨论 (https://community.iceoryx.io/t/safe-lock-free-primitives-with-iceoryx2s-byteatomic/19) - 在 Reddit 上讨论 (https://www.reddit.com/r/programming/comments/1vf68qb/safe_lockfree_primitives_with_iceoryx2s_byteatomic/) - 在 programming.dev 上讨论 (https://programming.dev/post/54554551) 1. https://github.com/rust-lang/rfcs/pull/3301↩ (https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#user-content-fnref-1) 2. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1478r7.html↩ (https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#user-content-fnref-2) 3. 使用 `copy_nonoverlapping` 将把问题转移到 `AtomicU8` 的创建上。↩ (https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#user-content-fnref-3)
相似文章
Lobsters Hottest
本文介绍了一种基于线性类型和抽象解释的内存安全新方法,旨在比Rust更符合人机工程学原理地消除诸如释放后使用和内存泄漏等常见错误。
Lobsters Hottest
Rust 的 SIMD 抽象现在允许在不使用 unsafe 代码的情况下安全使用,这得益于 Rust 1.87 引入的 CPU 特性令牌,从而实现了简洁且可移植的向量操作。
Hacker News Top
这篇博客文章解释了Rust中针对多线程结构的缓存感知数据布局,涵盖了字段分区和避免伪共享的128字节规则,并以一个SPSC环形缓冲区为例。
Lobsters Hottest
TokioConf 2026 的一篇演讲/博客文章探讨了如何通过为复杂指针结构实现追踪式垃圾回收,将安全 Rust 推向极限,并分享处理循环引用与原始指针 GC 设计的技巧。
Hacker News Top
# Rust 零拷贝页面:我是如何停止焦虑并爱上生命周期的
来源:[https://redixhumayun.github.io/databases/2026/04/14/zero-copy-pages-in-rust.html](https://redixhumayun.github.io/databases/2026/04/14/zero-copy-pages-in-rust.html)
*你可以在[这里](https://github.com/redixhumayun/simpledb/)找到该项目的源代码*
零拷贝是一种旨在消除内核与用户空间缓冲区之间 CPU 数据复制的技术,尤其在数据处理等高吞吐量应用中极具价值。