为什么在 Rust 中使用 AtomicU32,既然 U32 已经实现了 Sync?

Why use an AtomicU32 in Rust, given that U32 already implements Sync?

std::sync::atomic 模块包含许多原始类型的原子变体,声明的目的是这些类型现在是线程安全的。但是,所有对应于原子类型的原语都已经实现了 SendSync,因此应该已经是线程安全的了。 Atomic 类型背后的原因是什么?

通常,非原子整数可以安全地跨线程共享,因为它们是不可变的。如果您尝试修改该值,在大多数情况下您会隐式创建一个新值,因为它们是 Copy。但是,跨线程共享可变 referenceu32 是不安全的(或者对同一值同时具有可变和不可变引用),这实际上意味着您将无法修改变量并让另一个线程查看结果。原子类型具有一些使其安全的附加行为。

在更一般的情况下,使用非原子操作并不能保证在一个线程中所做的更改在另一个线程中可见。许多体系结构,尤其是 RISC 体系结构,不保证没有附加指令的行为。

此外,编译器经常在函数中重新排序对内存的访问,在某些情况下,跨函数,并且需要具有适当屏障的原子类型来向编译器指示这种行为是不需要的。

最后,通常需要原子操作来逻辑更新变量的内容。例如,我可能想自动将 1 加到一个变量中。在像 ARM 这样的加载-存储架构上,我不能用 add 指令修改内存的内容;我只能对寄存器进行算术运算。因此,一个原子加法是多条指令,通常包括一个加载链接指令,它加载一个内存位置,在寄存器上进行加法操作,然后是一个存储条件指令,如果内存位置没有改变,它存储值。如果有,还有一个循环重试。

这就是为什么需要原子操作并且通常跨语言有用的原因。因此,虽然 可以 在非 Rust 语言中使用非原子操作,但它们通常不会产生有用的结果,并且由于人们通常希望自己的代码能够正常运行,因此原子操作对于正确性。 Rust 的原子类型通过生成合适的指令来保证这种行为,因此可以安全地跨线程共享。