Skip to content

Commit 45a875f

Browse files
committed
修改第五章,稍微添加内存次序的“前言”介绍
1 parent 813212b commit 45a875f

File tree

1 file changed

+33
-0
lines changed

1 file changed

+33
-0
lines changed

md/05内存模型与原子操作.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,36 @@ void f(){
296296
`flag` 对象的状态为设置 (`true`) 时,其线程调用 `test_and_set` 函数会返回 `true`,导致它们继续在循环中自旋,无法退出。直到先前持有锁的线程调用 `unlock()` 函数,将 `flag` 对象的状态原子地更改为清除 (`false`) 状态。此时,等待的线程中会有一个线程成功调用 `test_and_set` 返回 `false`,然后退出循环,成功获取锁。
297297

298298
`std::atomic_flag` 的局限性太强,甚至不能当普通的 bool 标志那样使用。一般最好使用 `std::atomic<bool>`,下节,我们来使用它。
299+
300+
## 内存次序
301+
302+
### 前言
303+
304+
事实上我们在前面就用到了不少的内存次序,只不过一直没详细展开讲解。
305+
306+
在开始学习之前,我们需要强调一些基本的认识:
307+
308+
1. **内存次序是非常底层知识**:对于普通开发者来说,了解内存次序并非硬性需求。如果您主要关注业务开发,可以直接跳过本节内容。如果您对内存次序感兴趣,则需要注意其复杂性和难以观察的特性,这将使学习过程具有一定挑战性。
309+
310+
2. **内存次序错误的使用难以察觉**:即使通过多次(数以万计)运行也难以发现。这是因为许多内存次序问题是由于极端的、少见的情况下的竞争条件引起的,而这些情况很难被重现。此外,即使程序在某些平台上运行正常,也不能保证它在其他平台上也能表现良好,因为不同的 CPU 和编译器可能对内存操作的顺序有不同的处理(例如 x86 架构内存模型:Total Store Order (TSO),是比较严格的内存模型)。因此,开发者必须依赖自己的知识和经验,以及可能的测试和调试技术,来发现和解决内存次序错误。
311+
312+
造成错误难以被我们观察到的原因很朴素:
313+
314+
- **CPU 与编译器不是神经病,没有*好处*不会闲的没事给你重排**
315+
316+
你们可能还有疑问:“**单线程能不能指令重排**?”
317+
318+
其实完全是可以的,只不过不用在乎,不过我们还是可以稍微聊一下。用一个极端的例子来说:
319+
320+
-*end 重排到 start 前面了!指令重排了!*
321+
322+
有可能吗?完全有可能。但这也就是前面说的,把 CPU 与编译器当神经病。首先编译器优化需要遵守一个“如同规则”,即不可改变可观察的副作用(简单来说编译器优化不该影响程序结果)。另外 CPU 也有类似,不然各位写代码难道还要考虑下面这里,会不会指令重排导致先输出 `end` 吗?这显然不现实。
323+
324+
```txt
325+
print("start"); // 1
326+
print("end"); // 2
327+
```
328+
329+
但是为什么我说有可能呢?因为不禁止就是有可能,但是我们无需在乎,**就算真的 end 重排到 start 前面了,也得在可观测行为发生前回溯了**。所以我一直在强调,这些东西,**我们无需在意**
330+
331+
好了,到此,基本认识也就足够了,这些还是简单直接且符合直觉的。

0 commit comments

Comments
 (0)