原子操作
Wed Jun 21 2023 · 10min
概念
原子化操作是一种用于保证并发安全的技术,多用于多线程或多任务环境中。原子化操作通常由硬件支持,可以保证操作的原子性和线程之间的同步和一致性。在 Rust 中,原子化操作的具体实现可以使用 std::sync::atomic 模块中的 Atomic* 类型和 Ordering 枚举类型。
为什么由硬件支持呢? 因为底层需要调用到一些硬件指令集,例如 x86 架构中的 lock 前缀指令、ARM 架构中的 Load-Exclusive/Store-Exclusive 指令等。这些硬件指令可以保证原子化操作的原子性、顺序性和可见性,提高程序的效率和可靠性。
原子化操作(Atomic Operation)是指在执行过程中不会被中断的一个操作,要么全部执行成功,要么全部执行失败,不会出现部分执行的情况。在多线程、并发编程中,原子化操作能够保证数据的一致性和正确性。
如果多个线程同时对同一个变量进行操作,可能会导致数据的不一致性和错误。例如,线程 A 和线程 B 同时对变量 X 进行加 1 操作,如果这两个操作同时进行,可能会导致最终结果不是期望的结果。如果使用原子化操作,这个加 1 操作就会变成一个不可分割的整体,从而避免了多个线程同时操作同一个变量的问题。
原子化操作通常包括以下特征:
不可中断性:原子化操作执行时不会被中断,要么全部执行成功,要么全部执行失败。 原子性:原子化操作是一个不可分割的整体,不能被分割为多个步骤执行。 可见性:原子化操作对其他线程是可见的,其他线程能够立即看到原子化操作的结果。 有序性:原子化操作执行的顺序是有序的,不会受到其他线程的影响。 在实际编程中,原子化操作通常由硬件提供支持。常见的原子化操作包括原子加减操作、原子比较交换操作和原子读写操作等。
需要注意的是,尽管使用原子化操作能够提高程序的效率和可靠性,但是在程序设计和实现中,还需要考虑其他因素,例如锁的使用、线程安全性等。
Atomic crates
https://doc.rust-lang.org/stable/core/sync/atomic/enum.Ordering.html
Ordering
- Relaxed:表示没有同步或顺序要求,可以随意重排。这是最轻量级的内存模型,也是最快的,但是不能保证线程之间的同步和一致性。
- Acquire:表示对变量的读取操作需要保证在读取发生之后对同一个变量的后续读取和写入操作的顺序。这个模型用于在读取前确保访问的值是最新的,可以保证读取操作前的所有写入操作都已经完成,并且在读取操作完成后,可以保证所有使用被读取值的后续操作都是可见的。
- Release:表示对变量的写入操作需要保证在写入完成之前对同一个变量的后续写入和读取操作的顺序。这个模型用于在写入完成后确保访问的值是最新的,可以保证写入操作后的所有读取操作都可以访问到最新的值,并且在写入操作完成前,可以保证所有使用被写入值的前面操作都已经完成。
- AcqRel:表示对变量的读取和写入操作都需要保证有顺序要求,即前面执行的操作会在后面执行的操作之前完成。这个模型是 Acquire 和 Release 的结合,用于同时保证读取和写入的顺序。
- SeqCst(Sequentially Consistent):表示对变量的读取和写入操作需要保证所有线程都会观察到相同的顺序和结果,即所有线程中对该变量的操作都是按照某个全局的顺序排列的。这个模型是最严格的内存模型,保证同步和一致性,但是也是最慢的。
需要注意的是,使用 Atomic::Ordering 枚举类型时,选择合适的内存模型非常重要,不同的内存模型会对程序的正确性、性能和可维护性产生不同的影响。在实际编程中,需要根据具体情况选择合适的内存模型,以满足程序的需求。
Mutex
在 Rust 中,互斥器(Mutex)是一种常用的并发原语,用于在多线程环境中保证共享数据的安全访问。互斥器是一个同步工具,可以防止多个线程同时访问共享数据,从而避免竞态条件(Race Condition)的发生。
互斥器的工作原理是,在共享数据的访问前获取一个锁,并在使用完共享数据后释放这个锁。只有获得锁的线程才能访问共享数据,其他线程需要等待锁的释放才能访问共享数据。在 Rust 中,我们可以使用 std::sync::Mutex 类型来创建互斥器。