File tree Expand file tree Collapse file tree 3 files changed +53
-3
lines changed Expand file tree Collapse file tree 3 files changed +53
-3
lines changed Original file line number Diff line number Diff line change 44
55&emsp ;&emsp ; 在我们谈起“* 并发编程* ”,其实可以直接简单理解为“** 多线程编程** ”,我知道你或许有疑问:“那多进程呢?” C++ 语言层面没有进程的概念,并发支持库也不涉及多进程,所以在本教程中,不用在意。
66
7- &emsp ;&emsp ; 我们完全使用标准 C++ 进行教学。
7+ &emsp ;&emsp ; 我们主要使用标准 C++ 进行教学,也会稍微涉及一些其它库 。
88
99## 并发
1010
Original file line number Diff line number Diff line change 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++ 内存模型与原子操作。
Original file line number Diff line number Diff line change 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+ ```
You can’t perform that action at this time.
0 commit comments