Skip to content

Commit 63bbb99

Browse files
committed
1. 修改第一章与第二章小部分措辞与格式
2. 完成部分第四章内容,开始正文
1 parent 5ca44a5 commit 63bbb99

File tree

3 files changed

+53
-3
lines changed

3 files changed

+53
-3
lines changed

md/01基本概念.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
  在我们谈起“*并发编程*”,其实可以直接简单理解为“**多线程编程**”,我知道你或许有疑问:“那多进程呢?” C++ 语言层面没有进程的概念,并发支持库也不涉及多进程,所以在本教程中,不用在意。
66

7-
  我们完全使用标准 C++ 进行教学。
7+
  我们主要使用标准 C++ 进行教学,也会稍微涉及一些其它库
88

99
## 并发
1010

md/04同步操作.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 同步操作
22

3-
"同步操作"是指在计算机科学和信息技术中的一种操作方式,其中不同的任务或操作按顺序执行,一个操作完成后才能开始下一个操作。在同步操作中,各个任务之间通常需要相互**协调和等待**以确保**数据的一致性和正确性**
3+
"同步操作"是指在计算机科学和信息技术中的一种操作方式,其中不同的任务或操作按顺序执行,一个操作完成后才能开始下一个操作。在多线程编程中,各个任务通常需要通过同步操作进行相互**协调和等待**以确保数据的**一致性****正确性**
44

55
本章的主要内容有:
66

@@ -1120,6 +1120,6 @@ C++11 的 `std::this_thread::get_id()` 返回的内部类型没办法直接转
11201120
11211121
## 总结
11221122
1123-
在并发编程中,同步操作对于并发编程至关重要。如果没有同步,线程基本上就是独立的,因其任务之间的相关性,才可作为一个整体执行(比如第二章的并行求和)。本章讨论了多种用于同步操作的工具,包括条件变量、future、promise、package_task。同时,详细介绍了 C++ 时间库的知识,以使用并发支持库中的“限时等待”。最后使用 CMake + Qt 构建了一个带有 UI 界面的示例,展示异步多线程的必要性
1123+
在并发编程中,同步操作对于并发编程至关重要。如果没有同步,线程基本上就是独立的,因其任务之间的相关性,才可作为一个整体执行(比如第二章的并行求和)。本章讨论了多种用于同步操作的工具,包括条件变量、future、promise、package_task。同时,详细介绍了 C++ 时间库的知识,以使用并发支持库中的“限时等待”。最后使用 CMake + Qt 构建了一个带有 UI 界面的示例,展示异步多线程的**必要性**
11241124
11251125
在讨论了 C++ 中的高级工具之后,现在让我们来看看底层工具:C++ 内存模型与原子操作。
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,51 @@
11
# 内存模型与原子操作
2+
3+
- 内存模型定义了多线程程序中,读写操作如何在不同线程之间可见,以及这些操作在何种顺序下执行。内存模型确保程序的行为在并发环境下是可预测的。
4+
5+
- 原子操作即**不可分割的操作**。系统的所有线程,不可能观察到原子操作完成了一半。
6+
7+
最基础的概念就是如此,这里不再过多赘述,后续还会详细展开内存模型的问题。
8+
9+
## 原子操作
10+
11+
```cpp
12+
int a = 0;
13+
void f(){
14+
++a;
15+
}
16+
```
17+
18+
显然,`++a` 是非原子操作,也就是说在多线程中可能会被另一个线程观察到只完成一半。
19+
20+
1. 线程 A 和线程 B 同时开始修改变量 `a` 的值。
21+
2. 线程 A 对 `a` 执行递增操作,但还未完成。
22+
3. 在线程 A 完成递增操作之前,线程 B 也执行了递增操作。
23+
4. 线程 C 读取 `a` 的值。
24+
25+
线程 C 到底读取到多少不确定,a 的值是多少也不确定。显然,这构成了数据竞争,出现了[未定义行为](https://zh.cppreference.com/w/cpp/language/ub)
26+
27+
在之前的内容中,我们讲述了使用很多设施,如互斥量,来保护共享资源。
28+
29+
```cpp
30+
std::mutex m;
31+
void f() {
32+
std::lock_guard<std::mutex>{m};
33+
++a;
34+
}
35+
```
36+
37+
通过互斥量的保护,即使 `++a` 本身不是原子操作,**逻辑上也可视为原子操作**。互斥量确保了对共享资源的访问是线程安全的,避免了数据竞争问题。
38+
39+
不过这显然不是我们的重点,C++11 引入了原子类型 [`std::atomic`](https://zh.cppreference.com/w/cpp/atomic/atomic),在下文我们会详细讲解。
40+
41+
### 原子类型 `std::atomic`
42+
43+
标准原子类型定义在头文件 `<atomic>` 中。这些类型的操作都是原子的,语言定义中只有这些类型的操作是原子的,虽然也可以用互斥量来模拟原子操作(见上文)。标准的原子的类型实现可能是:*它们几乎都有一个 `is_lock_free()` 成员函数,这个函数可以让用户查询某原子类型的操作是直接用的原子指令(返回 `true`),还是内部用了锁实现(返回 `false`)。*
44+
45+
原子操作可以代替互斥量,来进行同步操作,也能带来更高的性能。但是如果它的内部使用互斥量实现,那么不可能有性能的提升。
46+
47+
在 C++17 中,所有原子类型都有一个 `static constexpr` 的数据成员 [`is_always_lock_free`](https://zh.cppreference.com/w/cpp/atomic/atomic/is_always_lock_free) 。如果当前环境上的原子类型 X 是无锁类型,那么 `X::is_always_lock_free` 将返回 `true` 。例如:
48+
49+
```cpp
50+
std::atomic<int>::is_always_lock_free // true 或 false
51+
```

0 commit comments

Comments
 (0)