在并发编程领域,Rust 提供了完善的机制来保证并发编程的安全,我们可以非常方便的使用 Mutex,Arc,Channel 等库来处理我们的并发逻辑。 但在有些时候,为了更高效的性能,我们可能会去写一些 lock-free 的数据结构,而 Rust 自身也提供了 atomic 的支持。
对于每个 atomic 操作,都需要显示的指定 Ordering,Rust 提供了 Relaxed,Release,Acquire,AcqRel,以及 SeqCst 这些 Ordering 的支持,使用不同的 Ordering 会让编译器或者 CPU 对某些指令进行重新排序执行,所以为了更正确的写出 lock-free 的代码,了解这些 Ordering 是如何工作的,就显得非常重要了。
Keywords
在开始介绍 Rust 的 memory ordering 之前,我们需要知道两个常用的用来描述atomic 操作之间关系的概念,synchronizes-with 和 happens-before:
Synchronizes-with - 简单来说,两个线程 A 和 B,以及一个支持原子操作的变量 x,如果 A 线程对 x 写入了一个新的值(store),而 B 线程在 x 上面读取到了这个新的值(load),我们就可以认为,A 的 store 就是 synchronizes-with B 的 load 的。
Happens-before - 这应该算是一个更基础的概念。如果一个操作 B 能看到之前操作 A 产生的结果,那么 A 就是 happens-before B 的。譬如在单线程里面,如果一个操作 A 的语句在操作 B 的前面执行,通常叫做 sequenced-before,那么 A 就是 happens-before B 的,譬如这样:
vec.push(1); // A
ready = true; // B
而对于跨线程(inter-thread) 的情况,要判断 happens-before,就需要借助于前面提到的 synchronizes-with 了。如果操作 A 是 synchronizes-with 另一个线程的操作 B 的,那么 A 就是 happens-before B 的。Happens-before 也具有传递性,如果 B 是 happens-before C 的,那么 A 也是 happens-before C。
如果 A 是 sequenced-before B,而 B 是 inter-thread happens-before C 的,那么 A 也是 inter-thread