|
|
|
@ -138,7 +138,7 @@ Y = 3; Y *= 2;
|
|
|
|
|
X = 2; }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
我们来讨论下以上线程状态下,`Y`最终的可能值(可能性依次降低):
|
|
|
|
|
我们来讨论下以上线程状态,`Y`最终的可能值(可能性依次降低):
|
|
|
|
|
|
|
|
|
|
- `Y = 3`: 线程`Main`运行完后才运行线程`A`,或者线程`A`运行完后再运行线程`Main`
|
|
|
|
|
- `Y = 6`: 线程`Main`的`Y = 3`运行完,但`X = 2`还没被运行, 此时线程A开始运行`Y *= 2`, 最后才运行`Main`线程的`X = 2`
|
|
|
|
@ -161,10 +161,10 @@ X = 2; }
|
|
|
|
|
在理解了内存顺序可能存在的改变后,你就可以明白为什么Rust提供了`Ordering::Relaxed`用于限定内存顺序了,事实上,该枚举有5个成员:
|
|
|
|
|
|
|
|
|
|
- **Relaxed**, 这是最宽松的规则,它对编译器和CPU不做任何限制,可以乱序
|
|
|
|
|
- **Release**,设定内存屏障(Memory barrier),指定屏障之前的数据不能被重新排序
|
|
|
|
|
- **Acquire**, 设定内存屏障,指定屏障之后的数据不能被重新排序,往往和`Release`在不同线程中联合使用
|
|
|
|
|
- **Release 释放**,设定内存屏障(Memory barrier),保证它之前的操作永远在它之前,但是它后面的操作可能被重排到它前面
|
|
|
|
|
- **Acquire 获取**, 设定内存屏障,保证在它之后的访问永远在它之后,但是它之前的操作却有可能被重排到它后面,往往和`Release`在不同线程中联合使用
|
|
|
|
|
- **AcqRel**, **Acquire**和**Release**的结合,同时拥有它们俩提供的保证。比如你要对一个 `atomic` 自增 1,同时希望该操作之前和之后的读取或写入操作不会被重新排序
|
|
|
|
|
- **SeqCst**, `SeqCst`就像是`AcqRel`的加强版,它不管原子操作是属于读取还是写入的操作,只要某个线程有用到`SeqCst`的原子操作,线程中该`SeqCst`操作前的数据操作绝对不会被重新排在该`SeqCst`操作之后,且该`SeqCst`操作后的数据操作也绝对不会被重新排在`SeqCst`操作前。
|
|
|
|
|
- **SeqCst 顺序一致性**, `SeqCst`就像是`AcqRel`的加强版,它不管原子操作是属于读取还是写入的操作,只要某个线程有用到`SeqCst`的原子操作,线程中该`SeqCst`操作前的数据操作绝对不会被重新排在该`SeqCst`操作之后,且该`SeqCst`操作后的数据操作也绝对不会被重新排在`SeqCst`操作前。
|
|
|
|
|
|
|
|
|
|
这些规则由于是系统提供的,因此其它语言提供的相应规则也大同小异,大家如果不明白可以看看其它语言的相关解释。
|
|
|
|
|
|
|
|
|
@ -217,6 +217,11 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
原则上,`Acquire`用于读取,而`Release`用于写入。但是由于有些原子操作同时拥有读取和写入的功能,此时就需要使用`AcqRel`来设置内存顺序了。在内存屏障中被写入的数据,都可以被其它线程读取到,不会有CPU缓存的问题。
|
|
|
|
|
|
|
|
|
|
**内存顺序的选择**
|
|
|
|
|
|
|
|
|
|
1. 不知道怎么选择时,优先使用`SeqCst`,虽然会稍微减慢速度,但是慢一点也比出现错误好
|
|
|
|
|
2. 多线程只计数`fetch_add`而不使用该值触发其他逻辑分支的简单使用场景,可以使用`Relaxed`
|
|
|
|
|
参考 [Which std::sync::atomic::Ordering to use?](https://stackoverflow.com/questions/30407121/which-stdsyncatomicordering-to-use)
|
|
|
|
|
|
|
|
|
|
## 多线程中使用Atomic
|
|
|
|
|
|
|
|
|
|