Skip to content

Commit 1a440ce

Browse files
committed
1. 勘误13700H能效核参数
2. 更新 `std::memory_order` 的内容 3. 确定内存模型后续的更新 4. 更新内存模型的内容,不同指令集架构的基础常识普及
1 parent 74bd178 commit 1a440ce

File tree

2 files changed

+92
-5
lines changed

2 files changed

+92
-5
lines changed

md/02使用线程.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ int main(){
7070

7171
当然了,都 2024 年了,我们还得考虑一个问题:“ *英特尔从 12 代酷睿开始,为其处理器引入了全新的“**大小核**”混合设计架构*”。
7272

73-
比如我的 CPU `i7 13700H` 它是 14 核心,20 线程,有 6 个能效核,6 个性能核。不过我们说了,物理核心这个*通常*不看重,`hardware_concurrency()` 输出的值会为 20。
73+
比如我的 CPU `i7 13700H` 它是 14 核心,20 线程,有 8 个能效核,6 个性能核。不过我们说了,物理核心这个*通常*不看重,`hardware_concurrency()` 输出的值会为 20。
7474

7575
- **在进行多线程编程时,我们可以参考此值来确定创建的线程数量,以更好地利用当前硬件,从而提升程序性能。**
7676

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

+91-4
Original file line numberDiff line numberDiff line change
@@ -685,9 +685,9 @@ print("end"); // 2
685685

686686
### 可见
687687

688-
可见 是多线程并发编程中的一个重要概念,它描述了一个线程中的数据修改对其他线程的可见程度。具体来说,如果线程 A 对变量 x 进行了修改,那么**其他线程 B 是否能够看到线程 A 对 x 的修改**,就涉及到可见的问题。
688+
**可见** 是 C++ 多线程并发编程中的一个重要概念,它描述了一个线程中的数据修改对其他线程的可见程度。具体来说,如果线程 A 对变量 x 进行了修改,那么**其他线程 B 是否能够看到线程 A 对 x 的修改**,就涉及到可见的问题。
689689

690-
在讨论多线程的内存模型和执行顺序时,虽然经常会提到 CPU 重排、编译器优化、缓存等底层细节,但真正核心的概念是可见,而不是这些底层实现细节。
690+
在讨论多线程的内存模型和执行顺序时,虽然经常会提到 CPU 重排、编译器优化、缓存等底层细节,但真正核心的概念是*可见*,而不是这些底层实现细节。
691691

692692
**C++ 标准中的可见**
693693

@@ -702,11 +702,12 @@ C++ 标准通过内存序(memory order)来定义如何确保这种*可见*
702702
- *可见* 关注的是线程之间的数据一致性,而不是底层的实现细节。
703703

704704
- 使用 C++ 的内存序机制可以确保数据修改的可见,而不必过多关注具体的 CPU 和编译器行为。
705-
这种描述方式可以帮助更清楚地理解和描述多线程并发编程中如何通过 C++ 标准的内存模型来确保线程之间的数据一致性。
705+
706+
这种描述方式可以帮助更清楚地理解和描述多线程并发编程中如何通过 C++ 标准的内存模型来确保线程之间的数据一致性,而无需太多关注底层细节。
706707

707708
---
708709

709-
我知道各位肯定有疑问,我们大多数时候写代码都从来没使用过内存序,一般都是互斥量、条件变量等高级设施,这没有可见性的问题吗?
710+
我知道各位肯定有疑问,我们大多数时候写多线程代码都从来没使用过内存序,一般都是互斥量、条件变量等高级同步设施,这没有可见性的问题吗?
710711

711712
没有,这些设施自动确保数据的可见性。例如: `std::mutex``unlock()` 保证:
712713

@@ -719,3 +720,89 @@ C++ 标准通过内存序(memory order)来定义如何确保这种*可见*
719720
也就是说:
720721

721722
- `std::mutex``unlock()` 操作*同步于*任何随后的 `lock()` 操作。这意味着,线程在调用 `unlock()` 时,对共享数据的修改会对之后调用 `lock()` 的线程*可见*
723+
724+
### `std::memory_order`
725+
726+
`std::memory_order` 是一个枚举类型,用来指定原子操作的内存顺序,影响这些操作的行为。
727+
728+
```cpp
729+
typedef enum memory_order {
730+
memory_order_relaxed,
731+
memory_order_consume,
732+
memory_order_acquire,
733+
memory_order_release,
734+
memory_order_acq_rel,
735+
memory_order_seq_cst
736+
} memory_order;
737+
738+
// C++20 起则为:
739+
740+
enum class memory_order : /* 未指明 */ {
741+
relaxed, consume, acquire, release, acq_rel, seq_cst
742+
};
743+
inline constexpr memory_order memory_order_relaxed = memory_order::relaxed;
744+
inline constexpr memory_order memory_order_consume = memory_order::consume;
745+
inline constexpr memory_order memory_order_acquire = memory_order::acquire;
746+
inline constexpr memory_order memory_order_release = memory_order::release;
747+
inline constexpr memory_order memory_order_acq_rel = memory_order::acq_rel;
748+
inline constexpr memory_order memory_order_seq_cst = memory_order::seq_cst;
749+
```
750+
751+
这 6 个常量,每一个常量都表示不同的内存次序。
752+
753+
大体来说我们可以将它们分为三类。
754+
755+
1. `memory_order_relaxed` 宽松定序:不是定序约束,**仅对此操作要求原子性**。
756+
2. `memory_order_seq_cst` 序列一致定序,这是库中所有原子操作的**默认行为**,也是**最严格的内存次序**,是**绝对安全**的。
757+
758+
剩下的就是第三类。
759+
760+
### 其它概念
761+
762+
<!--
763+
思考,似乎还是得聊一下 cppreference 中提到的那些基础概念。
764+
但是全写又太多,写的少点也不好弄,关键还得用自己的语言再修改一下。
765+
修改顺序:写写连贯、读读连贯、读写连贯、写读连贯;以及各种操作,消费操作、获得操作、释放操作,还是肯定得说一下的。
766+
-->
767+
768+
### `x86` 和 `ARM` 的内存模型:强一致性与弱一致性
769+
770+
<!--
771+
聊最广泛的 `x86` 与 `ARM` 这种,也顺便扯扯其它的架构,如 `RISC-V` 之类的,主要还是想稍微介绍下,聊点常识长长见识。
772+
-->
773+
774+
**内存模型是软件与实现之间的一种约定契约**。它定义了在多线程或并发环境中,如何对内存操作的顺序和一致性进行规范,以确保程序的正确性和可靠性。
775+
776+
C++ 标准为我们定义了 C++ 标准内存模型,使我们能够无需关心底层硬件环境就编写出跨平台的应用程序。不过,了解底层硬件架构的内存模型对扩展知识面和深入理解编程细节也非常有帮助。
777+
778+
最经典与常见的两种 CPU 指令集架构就是:`x86` 与 `ARM`。
779+
780+
- `x86` 架构:是一种复杂指令集计算([CISC](https://zh.wikipedia.org/wiki/%E8%A4%87%E9%9B%9C%E6%8C%87%E4%BB%A4%E9%9B%86%E9%9B%BB%E8%85%A6))架构,因其强大的性能被广泛应用于桌面电脑、笔记本电脑和服务器中。`x86` 架构采用的是 TSO(Total Store Order)[**内存一致性模型**](https://jamesbornholt.com/blog/memory-models/),是一种**强一致性模型**,**简化了多线程编程中的内存同步问题**(后文中会提到)。
781+
782+
- `ARM` 架构:是一种精简指令集计算([RISC](https://zh.wikipedia.org/wiki/%E7%B2%BE%E7%AE%80%E6%8C%87%E4%BB%A4%E9%9B%86%E8%AE%A1%E7%AE%97%E6%9C%BA))架构,因其高能效和低功耗特点广泛应用于移动设备、嵌入式系统和物联网设备中。`ARM` 架构采用的是**弱序内存模型**([weakly-ordered memory](https://developer.arm.com/documentation/102336/0100/Memory-ordering)),允许**更灵活**的内存优化,但这需要程序员使用内存屏障等机制来确保正确性。
783+
784+
这两种架构在设计理念和应用领域上存在显著差异,这也是它们在不同应用场景中表现出色的原因。
785+
786+
如果你从事嵌入式系统或者学术研究等,可能也听说过 `RISC-V` 架构,它目前在国内的应用也逐渐增多。
787+
788+
RISC-V 是一种开源的精简指令集计算(RISC)架构,旨在提供一种高效、模块化且开放的指令集。与 x86 和 ARM 架构不同,RISC-V 的设计目标是简化指令集,同时保持高度的灵活性和扩展性。它在内存模型方面也有自己独特的特性。
789+
790+
RISC-V 采用的也是**弱序内存模型**(weakly-ordered memory model),这与 x86 的强一致性模型(TSO)和 ARM 的弱一致性模型有所不同。你可能会有疑问:
791+
792+
- `ARM` 和 `RISC-V` 都是弱序内存模型,为什么不同?
793+
794+
各位一定要区分,这种强弱其实也只是一种分类而已,不同的指令集架构大多都还是有所不同的,并不会完全一样。例如: `x86` 的 TSO(Total Store Order)是强一致性模型的一种,但并不是所有强一致性模型都是 TSO
795+
796+
### 宽松定序
797+
798+
### 释放-获取定序
799+
800+
### 释放-消费定序
801+
802+
### 序列一致定序
803+
804+
### 与 `volatile` 的关系
805+
806+
<!--
807+
以上 5 节是根据 cppreference 的顺序,我们参考 cppreference 的描述,并根据自己的想法和语言
808+
-->

0 commit comments

Comments
 (0)