Skip to content

Latest commit

 

History

History
76 lines (57 loc) · 2.65 KB

File metadata and controls

76 lines (57 loc) · 2.65 KB

Table of Contents generated with DocToc

数据访问

假设要实现一个计数功能,每次对变量执行+1的操作。CPU执行的时候就需要顺序执行三个操作

  1. 从内存中读取变量的值
  2. 加1
  3. 写回到内存中

如果有两个线程同时操作了这个值,这三个操作可能是交叉的,导致结果不正确

指令重排

fn f(a: &mut i32, b: &mut i32) {
    *a += 1;
    *b += 1;
    *a += 1;
}

交给操作系统编译执行,但很可能你得到的是这样的

fn f(a: &mut i32, b: &mut i32) {
    *a += 2;
    *b += 1;
}

只要不影响程序语义,指令可以重排执行以优化,即不按代码顺序执行。

单线程下这样问题可能还不大,但如果多线程下,同一线程下多条原子指令,也是会有指令重排的可能,数据竞争很有可能发生,就是说加了原子操作也无法确定数据操作顺序

ordering

Rust用于的内存访问顺序(memory order)的Ordering基本和C++ 20的内存排序的保持一致

#[stable(feature = "rust1", since = "1.0.0")]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
#[rustc_diagnostic_item = "Ordering"]
pub enum Ordering {
    Relaxed,
    Release,
    Acquire,
    AcqRel,
    SeqCst,
}
  • Relaxed,这是最宽松的规则,它对编译器和 CPU 不做任何限制,可以乱序执行。
  • Release,适用于写数据操作
    • 当前线程不能有其他的读或写被 reorder 在 store 之后当前写入后的结果对其他线程的同一数据 Acquire 读取操作是可见的
  • Acquire,适用于读取数据操作
    • 当前线程不能有其他的读或写被 reorder 在 load 之前其他线程的同一数据已发生的 Release 写入操作都是对其可见的
  • AcqRel ,是 Acquire 和 Release 的结合
    • 一般用在 fetch_xxx 上,比如你要对一个 atomic 自增 1,你希望这个操作之前和之后的读取或写入操作不会被乱序,并且操作的结果对其它线程可见。
  • SeqCst,除了 AcqRel 的保证外,它还保证所有线程看到的所有 SeqCst 操作的顺序是一致的。

Ordering 的可见性

案例一