├── .gitignore
├── LICENSE
├── README.md
├── SUMMARY.md
├── book.json
├── content
├── Data-Structures
│ ├── 10.1-chinese.md
│ └── 10.2-chinese.md
├── Further-Information
│ ├── 11.1-chinese.md
│ ├── 11.2-chinese.md
│ ├── 11.3-chinese.md
│ └── 11.4-chinese.md
├── History-Quick-Overview.md
├── How-you-should-read-the-book.md
├── Pattrns
│ ├── Best-Practices
│ │ ├── 9.0-chinese.md
│ │ ├── 9.1-chinese.md
│ │ ├── 9.2-chinese.md
│ │ └── 9.3-chinese.md
│ ├── Concurrent-Architecture
│ │ ├── 8.0-chinese.md
│ │ ├── 8.1-chinese.md
│ │ ├── 8.2-chinese.md
│ │ └── 8.3-chinese.md
│ ├── Patterns-and-Best-Practices
│ │ ├── 6.0-chinese.md
│ │ ├── 6.1-chinese.md
│ │ ├── 6.2-chinese.md
│ │ ├── 6.3-chinese.md
│ │ └── 6.4-chinese.md
│ └── Synchronisation-Patterns
│ │ ├── 7.0-chinese.md
│ │ ├── 7.1-chinese.md
│ │ └── 7.2-chinese.md
├── Reader-Testimonials.md
├── Source-Code.md
└── The-Details
│ ├── Case-Studies
│ ├── 4.0-chinese.md
│ ├── 4.1-chinese.md
│ ├── 4.2-chinese.md
│ ├── 4.3-chinese.md
│ └── 4.4-chinese.md
│ ├── Memory-Model
│ ├── 1.0-chinese.md
│ ├── 1.1-chinese.md
│ ├── 1.2-chinese.md
│ ├── 1.3-chinese.md
│ ├── 1.4-chinese.md
│ └── 1.5-chinese.md
│ ├── Multithreading
│ ├── 2.0-chinese.md
│ ├── 2.1-chinese.md
│ ├── 2.2-chinese.md
│ ├── 2.3-chinese.md
│ ├── 2.4-chinese.md
│ └── 2.5-chinese.md
│ ├── Parallel-Algorithms-of-the-Standard
│ ├── 3.0-chinese.md
│ ├── 3.1-chinese.md
│ ├── 3.2-chinese.md
│ ├── 3.3-chinese.md
│ └── 3.4-chinese.md
│ └── The-Future-CPP-20-23
│ ├── 5.0-chinese.md
│ ├── 5.1-chinese.md
│ ├── 5.2-chinese.md
│ ├── 5.3-chinese.md
│ ├── 5.4-chinese.md
│ ├── 5.5-chinese.md
│ ├── 5.6-chinese.md
│ ├── 5.7-chinese.md
│ └── 5.8-chinese.md
├── cover.jpg
└── images
├── Further-Information
├── Challenges
│ ├── 1.png
│ ├── 10.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ ├── 8.png
│ └── 9.png
├── CppMem
│ ├── 1.png
│ └── 2.png
└── The-Time-Library
│ ├── 1.png
│ ├── 10.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ ├── 8.png
│ └── 9.png
├── History-Quick-Overview
├── 0.png
├── 1.png
├── 2.png
└── 3.png
├── Patterns
├── Best-Practices
│ ├── 1.png
│ ├── 10.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ ├── 8.png
│ └── 9.png
├── Concurrent-Architecture
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ └── 8.png
└── Synchronisation-Patterns
│ ├── 1.png
│ ├── 10.png
│ ├── 11.png
│ ├── 12.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ ├── 8.png
│ └── 9.png
└── detail
├── Case-Studies
├── 10.png
├── 11.png
├── 12.png
├── 13.png
├── 14.png
├── 15.png
├── 16.png
├── 17.png
├── 18.png
├── 19.png
├── 20.png
├── 21.png
├── 22.png
├── 23.png
├── 24.png
├── 25.png
├── 26.png
├── 27.png
├── 28.png
├── 29.png
├── 30.png
├── 31.png
├── 32.png
├── 33.png
├── 34.png
├── 35.png
├── 36.png
├── 37.png
├── 38.png
├── 39.png
├── 40.png
├── 41.png
├── 42.png
├── 43.png
├── 44.png
├── 45.png
├── 46.png
├── 47.png
├── 48.png
├── 49.png
├── 50.png
├── 51.png
├── 52.png
├── 53.png
├── 54.png
└── 55.png
├── Parallel-Algorithms-of-the-Standard
├── 1.png
├── 2.png
├── 3.png
├── 4.png
├── 5.png
├── 6.png
├── 7.png
├── 8.png
└── 9.png
├── The-Future-CPP-20-23
├── 1.png
├── 10.png
├── 11.png
├── 12.png
├── 2.png
├── 3.png
├── 4.png
├── 5.png
├── 6.png
├── 7.png
├── 8.png
└── 9.png
├── memory-model
├── 1.png
├── 10.png
├── 11.png
├── 12.png
├── 13.png
├── 14.png
├── 15.png
├── 16.png
├── 17.png
├── 18.png
├── 19.png
├── 2.png
├── 20.png
├── 21.png
├── 22.png
├── 23.png
├── 24.png
├── 25.png
├── 26.png
├── 27.png
├── 28.png
├── 29.png
├── 3.png
├── 30.png
├── 31.png
├── 32.png
├── 4.png
├── 5.png
├── 6.png
├── 7.png
├── 8.png
└── 9.png
└── multithreading
├── 1.png
├── 10.png
├── 11.png
├── 12.png
├── 13.png
├── 14.png
├── 15.png
├── 16.png
├── 17.png
├── 18.png
├── 19.png
├── 2.png
├── 20.png
├── 21.png
├── 22.png
├── 23.png
├── 24.png
├── 25.png
├── 26.png
├── 27.png
├── 28.png
├── 29.png
├── 3.png
├── 30.png
├── 31.png
├── 32.png
├── 33.png
├── 34.png
├── 4.png
├── 5.png
├── 6.png
├── 7.png
├── 8.png
└── 9.png
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pdf
2 | /_book/
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Concurrency with Modern C++
2 |
3 | * 作者:Rainer Grimm
4 | * 译者:陈晓伟
5 | * 原文发布时间:2019年03月19日
6 |
7 | > 翻译是译者用自己的思想,换一种语言,对原作者想法的重新阐释。鉴于我的学识所限,误解和错译在所难免。如果你能买到本书的原版,且有能力阅读英文,请直接去读原文。因为与之相较,我的译文可能根本不值得一读。
8 | >
9 | >
— 云风,程序员修炼之道第2版译者
10 |
11 | ## 本书概述
12 |
13 | 每个专业的C++开发者,都应该知晓的并发性。
14 |
15 | 本书是一场关于C++并发的旅程。
16 |
17 | * C++11和C++14创建了并发和并行的基础件。
18 |
19 | * C++17中,将标准模板库(STL)的大部分算法并行化。这意味着大多数基于STL的算法可以串行、并行或向量化执行。
20 |
21 | * C++的并发之旅并没有停止。C++20/23中还有增强版future、协程([coroutines](https://en.cppreference.com/w/cpp/language/coroutines))、事件性内存([transactional_memory](https://en.cppreference.com/w/cpp/language/transactional_memory))等等。
22 |
23 | 本书解释了C++中的并发性,并提供了许多代码示例。因此,可以将理论与实践相结合。
24 |
25 | 因为这本书与并发相关,所以我展示了很多容易出错的地方,并展示避免或解决它们的方案。
26 |
27 | ## 书与作者
28 |
29 | 这本书使用英语完成。在写书之前,我在我的英文博客www.ModernesCpp.com发布了要写这本书的消息,并得到了很多人的回复。有大概有50多个人要帮我校对。特别感谢我的闺女Juliette,对本书的布局进行升华;还有我的儿子,你是本书的第一个审阅者哦。当然,还有很多很多人 : NikosAthanasiou, RobertBadea, JoeDas, Jonas Devlieghere, Randy Hormann, Lasse Natvig, Erik Newton, Ian Reeve, Bart Vandewoestyne, Dafydd Walters, Andrzej Warzynski, 以及Enrico Zschemisch。
30 |
31 | 我已经做了20多年的软件架构师、团队带头人和讲师。在业余时间,我喜欢了解关于C++、Python和Haskell的信息。2016年时,我决定为自己工作。我会组织关于C++和Python的研讨会。
32 |
33 | 在Oberstdorf时,我换了一个新的髋关节(义肢)。本书的前半部分是在诊所期间所写,这段时间充满挑战,对我写书也有很大的帮助。
34 |
35 | ## 本书相关
36 |
37 | * github翻译地址:https://github.com/xiaoweiChen/Concurrency-with-Modern-Cpp
38 | * 英文原版PDF:https://ru.b-ok2.org/book/5247958/3b69d3
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # 目录
2 |
3 | * [读者推荐](content/Reader-Testimonials.md)
4 | * [代码说明](content/Source-Code.md)
5 | * [如何阅读](content/How-you-should-read-the-book.md)
6 | * [C++并发历史概述](content/History-Quick-Overview.md)
7 | * 详细介绍
8 | * [内存模型](content/The-Details/Memory-Model/1.0-chinese.md)
9 | * [内存模型的基础知识](content/The-Details/Memory-Model/1.1-chinese.md)
10 | * [编程协议](content/The-Details/Memory-Model/1.2-chinese.md)
11 | * [原子操作](content/The-Details/Memory-Model/1.3-chinese.md)
12 | * [同步和顺序](content/The-Details/Memory-Model/1.4-chinese.md)
13 | * [栅栏(Fences)](content/The-Details/Memory-Model/1.5-chinese.md)
14 | * [多线程](content/The-Details/Multithreading/2.0-chinese.md)
15 | * [线程](content/The-Details/Multithreading/2.1-chinese.md)
16 | * [共享数据](content/The-Details/Multithreading/2.2-chinese.md)
17 | * [线程-本地数据](content/The-Details/Multithreading/2.3-chinese.md)
18 | * [条件变量](content/The-Details/Multithreading/2.4-chinese.md)
19 | * [任务](content/The-Details/Multithreading/2.5-chinese.md)
20 | * [标准库的并行算法](content/The-Details/Parallel-Algorithms-of-the-Standard/3.0-chinese.md)
21 | * [执行策略](content/The-Details/Parallel-Algorithms-of-the-Standard/3.1-chinese.md)
22 | * [算法](content/The-Details/Parallel-Algorithms-of-the-Standard/3.2-chinese.md)
23 | * [新算法](content/The-Details/Parallel-Algorithms-of-the-Standard/3.3-chinese.md)
24 | * [性能概况](content/The-Details/Parallel-Algorithms-of-the-Standard/3.4-chinese.md)
25 | * [案例研究](content/The-Details/Case-Studies/4.0-chinese.md)
26 | * [求向量元素的加和](content/The-Details/Case-Studies/4.1-chinese.md)
27 | * [单例模式:线程安全的初始化](content/The-Details/Case-Studies/4.2-chinese.md)
28 | * [使用CppMem进行优化](content/The-Details/Case-Studies/4.3-chinese.md)
29 | * [总结](content/The-Details/Case-Studies/4.4-chinese.md)
30 | * [C++20/23的特性](content/The-Details/The-Future-CPP-20-23/5.0-chinese.md)
31 | * [关于执行](content/The-Details/The-Future-CPP-20-23/5.1-chinese.md)
32 | * [可协作中断的线程](content/The-Details/The-Future-CPP-20-23/5.2-chinese.md)
33 | * [原子智能指针](content/The-Details/The-Future-CPP-20-23/5.3-chinese.md)
34 | * [扩展特性](content/The-Details/The-Future-CPP-20-23/5.4-chinese.md)
35 | * [门闩和栅栏](content/The-Details/The-Future-CPP-20-23/5.5-chinese.md)
36 | * [协程](content/The-Details/The-Future-CPP-20-23/5.6-chinese.md)
37 | * [事务性内存](content/The-Details/The-Future-CPP-20-23/5.7-chinese.md)
38 | * [任务块](content/The-Details/The-Future-CPP-20-23/5.8-chinese.md)
39 | * 模式
40 | * [模式和最佳实践](content/Pattrns/Patterns-and-Best-Practices/6.0-chinese.md)
41 | * [相关历史](content/Pattrns/Patterns-and-Best-Practices/6.1-chinese.md)
42 | * [价值所在](content/Pattrns/Patterns-and-Best-Practices/6.2-chinese.md)
43 | * [模式与最佳实践](content/Pattrns/Patterns-and-Best-Practices/6.3-chinese.md)
44 | * [反模式](content/Pattrns/Patterns-and-Best-Practices/6.4-chinese.md)
45 | * [同步模式](content/Pattrns/Synchronisation-Patterns/7.0-chinese.md)
46 | * [处理共享](content/Pattrns/Synchronisation-Patterns/7.1-chinese.md)
47 | * [处理突变](content/Pattrns/Synchronisation-Patterns/7.2-chinese.md)
48 | * [并发架构](content/Pattrns/Concurrent-Architecture/8.0-chinese.md)
49 | * [活动对象](content/Pattrns/Concurrent-Architecture/8.1-chinese.md)
50 | * [监控对象](content/Pattrns/Concurrent-Architecture/8.2-chinese.md)
51 | * [半同步/半异步](content/Pattrns/Concurrent-Architecture/8.3-chinese.md)
52 | * [最佳实践](content/Pattrns/Best-Practices/9.0-chinese.md)
53 | * [通常情况](content/Pattrns/Best-Practices/9.1-chinese.md)
54 | * [多线程](content/Pattrns/Best-Practices/9.2-chinese.md)
55 | * [内存模型](content/Pattrns/Best-Practices/9.3-chinese.md)
56 | * 数据结构
57 | * [有锁结构](content/Data-Structures/10.1-chinese.md)
58 | * [无锁结构](content/Data-Structures/10.2-chinese.md)
59 | * 更多信息
60 | * [挑战](content/Further-Information/11.1-chinese.md)
61 | * [时间库](content/Further-Information/11.2-chinese.md)
62 | * [CppMem-概述](content/Further-Information/11.3-chinese.md)
63 | * [术语表](content/Further-Information/11.4-chinese.md)
64 |
65 |
--------------------------------------------------------------------------------
/book.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "《Concurrency with Modern C++》中文版",
3 |
4 | "author" : "陈晓伟 chenxiaowei",
5 |
6 | "description":"作为对《Concurrency with Modern C++》英文版的中文翻译。",
7 |
8 | "language":"zh-hans",
9 |
10 | "direction":"ltr",
11 |
12 | "pluginsConfig": {
13 | "fontSettings": {
14 | "theme": "white",
15 | "family": "msyh",
16 | "size": 2
17 | },
18 | "plugins": [
19 | "katex",
20 | "ace"
21 | ]
22 | },
23 |
24 | "pdf": {
25 | "pageNumbers": false,
26 | "paperSize": "a4",
27 | "margin": {
28 | "right": 10,
29 | "left": 10,
30 | "top": 10,
31 | "bottom": 20
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/content/Data-Structures/10.1-chinese.md:
--------------------------------------------------------------------------------
1 | #有锁结构
2 |
3 |
--------------------------------------------------------------------------------
/content/Data-Structures/10.2-chinese.md:
--------------------------------------------------------------------------------
1 | #无锁结构
--------------------------------------------------------------------------------
/content/Further-Information/11.3-chinese.md:
--------------------------------------------------------------------------------
1 | # CppMem-概述
2 |
3 | [CppMem](http://svr-pes20-cppmem.cl.cam.ac.uk/cppmem/)是一个交互式工具,用于对C++小代码段的内存模型行为进行研究。它应该是每个认真处理内存模型程序员的必备工具。
4 |
5 | CppMem的网上版本(也可以把它安装在你的个人电脑上)以两种方式提供服务:
6 |
7 | 1. CppMem验证小代码段的行为,基于选择的C++内存模型,该工具考虑所有可能的线程交错,将每个线程可视化到一个图中,并用附加的细节对这些图进行注释。
8 | 2. CppMem的精确分析,可以更加深入了解C++内存模型。简言之,CppMem是一个帮助理解内存模型的工具。
9 |
10 | 当然,必须跨过一些门槛,这通常是强大工具的共性。CppMem的本质是提供与这个极具挑战性的主题相关的非常详细的分析,并且是高度可配置的。因此,我才打算介绍该工具的各种组件。
11 |
12 | ## 简单概述
13 |
14 | 我对CppMem的简单概述是基于默认配置的。这篇概述只是提供了进一步的实验基础,应该有助于理解我正在进行的优化过程。
15 |
16 | 
17 |
18 | 简单起见,我引用了屏幕截图中的红色数字。
19 |
20 | ### 1. Model模型
21 |
22 | * 指定C++内存模型。首选是C++11内存模型的一个(简化)等价的变体。
23 |
24 | ### 2. Program 程序
25 |
26 | * 包含可执行程序,其语法类似于简化的C++11。确切地说,不能直接将C或C++代码程序复制到CppMem中。
27 | * 可以在许多典型多线程场景之间进行切换。要获得这些程序的详细信息,请阅读这篇写得非常好的文章,该文章将[C++并发性数学化](http://www.cl.cam.ac.uk/~pes20/cpp/popl085ap-sewell.pdf)。当然,也可以运行自己的代码。
28 | * CppMem是关于多线程的,所以可以使用多线程的快捷方式。
29 | * 可以使用表达式`{ { {…|||…} } }`。三个点`(…)`表示每个线程的工作包。
30 | * 如果使用表达式`x.readvalue(1)`,则CppMem会计算线程交错的情况,其中线程会为`x`赋值1。
31 |
32 | ### 3. Display Relations 关系显示
33 |
34 | * 描述原子操作、栅栏和锁上的读、写和读写改之间的关系。
35 | * 可以使用复选框显式地启用带注释的图中的关系。
36 | * 有三种关系,最有趣的是原始关系和派生关系之间的粗略区别。这里使用的是默认值。
37 | * 渊源关系:
38 | * sb: sequenced-before 序前
39 | * rf: read from 读取
40 | * mo: modification order 修改顺序
41 | * sc: sequentially consistent 按顺序一致
42 | * lo: lock order 锁定顺序
43 | * 派生关系:
44 | * sw: synchronises-with 与...同步
45 | * dob: dependency-ordered-before 序前依赖
46 | * unsequenced_races: 单线程中的竞争
47 | * data_races: 线程内的数据竞争
48 |
49 | ### 4. Display Layout 布局显示
50 |
51 | * 可以选择使用哪个[Doxygraph](https://sourceforge.net/projects/doxygraph/)图形。
52 |
53 | ### 5. Model Predicates 模型谓词
54 |
55 | * 使用此按钮,可以为所选模型设置谓词,这会导致不一致(非无数据争用)的执行,所以当执行不一致,就会看到不一致执行的原因。我在这本书里不使用这个按钮。
56 |
57 | 有关更多细节,请参阅[文档](http://svr-pes20-cppmem.cl.cam.ac.uk/cppmem/help.html)。
58 |
59 | 作为对CppMem的入门,这就足够了。现在,是时候尝试一下CppMem了。
60 |
61 | CppMem提供了许多示例。
62 |
63 | ### 示例
64 |
65 | 这些示例展示了使用并发代码,特别是使用无锁代码时的典型用例。可以将这些例子,分成几类。
66 |
67 | **论文**
68 |
69 | 示例/论文类别为您提供了一些示例,这些示例在本文中对[C++并发性的数学化](https://www.cl.cam.ac.uk/~pes20/cpp/popl085ap-sewell.pdf)进行了深入的讨论。
70 |
71 | * data_race.c : x上的数据竞争
72 | * partial_sb.c : 单线程中计算的序前
73 | * unsequenced_race.c : 根据评价顺序,对x上未排序的竞争进行评价
74 | * sc_atomics.c : 正确的使用原子变量
75 | * thread_create_and_asw.c : 额外的同步——与适当的线程创建同步
76 |
77 | 让我们从第一个示例开始。
78 |
79 | **测试运行**
80 |
81 | 从CppMem样本中选择data_race.c程序。run之后,立即显示有一个数据竞争。
82 |
83 | 
84 |
85 | 简单起见,只解释示例中的红色数字。
86 |
87 | 1. 很容易观察到的数据竞争。一个线程写`x (x==3)`,另一个线程不同步读`x (x==3)`。
88 | 2. 由于C++内存模型,两个线程可能交织在一起运行,其中只有一个与所选模型一致。如果在表达式`x==3`中的`x`,在主函数中进行赋值`int x= 2`,则会出现这种情况。图中在用`rf`和`sw`标注的边缘显示了这种关系。
89 | 3. 不同的线程交错之间切换显得非常有趣。
90 | 4. 该图显示关系中启用的所有关系。
91 | * `a:Wna x=2`在图表中是第`a`中表述,它是非原子性的。`Wna`表示“非原子写入”。
92 | * 图中的关键是`x (b:Wna)`的写和`x (C:Rna)`的读之间的连线。这也就是`x`上的数据竞争。
93 |
94 | **进一步分类**
95 |
96 | 进一步的分类会关注于无锁编程的方面。每个类别的示例都有不同的形式,每个表单使用不同的内存顺序。有关类别的更多讨论,请阅读前面提到的[将C++并发性数学化](https://www.cl.cam.ac.uk/~pes20/cpp/popl085ap-sewell.pdf)的文章。如果可能的话,我会用顺序一致性来表示程序。
97 |
98 | **存储缓冲(示例/SB_store_buffering)**
99 |
100 | 两个线程分别写入不同的位置,然后从另一个位置读取。
101 |
102 | SB+sc_sc+sc_sc+sc.c
103 |
104 | ```c++
105 | // SB+sc_sc+sc_sc
106 | // Store Buffering (or Dekker's), with all four accesses SC atomics
107 | // Question: can the two reads both see 0 in the same execution?
108 | int main() {
109 | atomic_int x=0; atomic_int y=0;
110 | {{{ { y.store(1,memory_order_seq_cst);
111 | r1=x.load(memory_order_seq_cst); }
112 | ||| { x.store(1,memory_order_seq_cst);
113 | r2=y.load(memory_order_seq_cst); } }}}
114 | return 0;
115 | }
116 | ```
117 |
118 | **消息传递(示例/MP_message_passing)**
119 |
120 | 一个线程写入数据(非原子变量)并设置一个原子标志,而另一个线程等待读取数据标志(非原子变量)。
121 |
122 | MP+na_sc+sc_na.c
123 |
124 | ```c++
125 | // MP+na_sc+sc_na
126 | // Message Passing, of data held in non-atomic x,
127 | // with sc atomic stores and loads on y giving release/acquire synchronisation
128 | // Question: is the read of x required to see the new data value 1
129 | // rather than the initial state value 0?
130 | int main() {
131 | int x=0; atomic_int y=0;
132 | {{{ { x=1;
133 | y.store(1,memory_order_seq_cst); }
134 | ||| { r1=y.load(memory_order_seq_cst).readsvalue(1);
135 | r2=x; } }}}
136 | return 0;
137 | }
138 | ```
139 |
140 | **读取缓冲(例子/LB_load_buffering)**
141 |
142 | 两个读操作可以看到之后的其他线程的写操作吗?
143 |
144 | Lb+sc_sc+sc_sc.c
145 |
146 | ```c++
147 | // LB+sc_sc+sc_sc
148 | // Load Buffering, with all four accesses sequentially consistent atomics
149 | // Question: can the two reads both see 1 in the same execution?
150 | int main() {
151 | atomic_int x=0; atomic_int y=0;
152 | {{{ { r1=x.load(memory_order_seq_cst);
153 | y.store(1,memory_order_seq_cst); }
154 | ||| { r2=y.load(memory_order_seq_cst);
155 | x.store(1,memory_order_seq_cst); } }}}
156 | return 0;
157 | }
158 | ```
159 |
160 | **从写到读的因果关系(例子/WRC)**
161 |
162 | 第三个线程是否看到第一个线程的写操作?
163 |
164 | * 第一个线程写x。
165 | * 第二个线程从中读取数据并写入到y。
166 | * 第三个线程读取x。
167 |
168 | WRC+rel+acq_rel+acq_rlx.c
169 |
170 | ```c++
171 | // WRC
172 | // the question is whether the final read is required to see 1
173 | // With two release/acquire pairs, it is
174 | int main() {
175 | atomic_int x = 0;
176 | atomic_int y = 0;
177 | {{{ x.store(1,mo_release);
178 | ||| { r1=x.load(mo_acquire).readsvalue(1);
179 | y.store(1,mo_release); }
180 | ||| { r2=y.load(mo_acquire).readsvalue(1);
181 | r3=x.load(mo_relaxed); }
182 | }}}
183 | return 0;
184 | }
185 | ```
186 |
187 | **独立读-独立写(示例\IRIW)**
188 |
189 | 两个线程写入不同的位置,第二个线程能以不同的顺序看到写操作吗?
190 |
191 | IRIW+rel+rel+acq_acq+acq_acq.c
192 |
193 | ```c++
194 | // IRIW with release/acquire
195 | // the question is whether the reading threads have
196 | // to see the writes to x and y in the same order.
197 | // With release/acquire, they do not.
198 | int main() {
199 | atomic_int x = 0; atomic_int y = 0;
200 | {{{ x.store(1, memory_order_release);
201 | ||| y.store(1, memory_order_release);
202 | ||| { r1=x.load(memory_order_acquire).readsvalue(1);
203 | r2=y.load(memory_order_acquire).readsvalue(0); }
204 | ||| { r3=y.load(memory_order_acquire).readsvalue(1);
205 | r4=x.load(memory_order_acquire).readsvalue(0); }
206 | }}};
207 | return 0;
208 | }
209 | ```
210 |
211 |
--------------------------------------------------------------------------------
/content/Further-Information/11.4-chinese.md:
--------------------------------------------------------------------------------
1 | # 术语表
2 |
3 | 本术语表只为基本术语提供参考。
4 |
5 | ## ACID
6 |
7 | 事务具有原子性、一致性、隔离性和持久性(ACID)属性的操作。在C++中,除了持久性之外,事务性内存的所有属性都保持不变。
8 |
9 | * 原子性:执行或不执行块的所有语句。
10 | * 一致性:系统始终处于一致的状态,所有事务构建顺序一致。
11 | * 独立性:每个事务在完全隔离的情况下运行。
12 | * 会对事务的持久性进行记录。
13 |
14 | ## CAS
15 |
16 | CAS表示compare-and-swap,是一个原子操作。它将内存位置与给定值进行比较,如果内存位置与给定值相同,则修改内存位置的值。在C++中,CAS操作有`std::compare_exchange_strong`和`std::compare_exchange_weak`。
17 |
18 | ## 可调用单元
19 |
20 | 可调用单元的行为类似于函数。不仅是函数,还有函数对象和Lambda函数。如果一个可调用单元接受一个参数,它就被称为一元可调用单元;如果有两个参数,就是二元可调用单元。
21 |
22 | 谓词是返回布尔值的特殊可调用项。
23 |
24 | ## 并发性
25 |
26 | 并发性意味着多个任务的重叠执行。而且,并发是并行的超集。
27 |
28 | ## 临界区
29 |
30 | 临界区是一段代码,最多只有一个线程可以访问。
31 |
32 | ## 立即求值
33 |
34 | 如果立即求值,则立即求出表达式的值,则该策略与延迟求值正交。立即求值通常也称为贪婪求值。
35 |
36 | ## Executor
37 |
38 | 执行者是与特定执行上下文相关联的对象。它提供一个或多个执行函数,用于为可调用的函数对象创建执行代理。
39 |
40 | ## 函数对象
41 |
42 | 首先,不要叫它们[函子](https://en.wikipedia.org/wiki/Functor)。这是一个明确的数学术语,叫做[范畴理论](https://en.wikipedia.org/wiki/Category_theory)。
43 |
44 | 函数对象是行为类似于函数,通过实现函数调用操作符来实现这一点。由于函数对象是对象,因此可以有属性和状态。
45 |
46 | ```c++
47 | struct Square{
48 | void operator()(int& i){i= i*i;}
49 | };
50 |
51 | std::vector myVec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
52 |
53 | std::for_each(myVec.begin(), myVec.end(), Square());
54 |
55 | for (auto v: myVec) std::cout << v << " "; // 1 4 9 16 25 36 49 64 81 100
56 | ```
57 |
58 | > **实例化函数对象**
59 | >
60 | > 常见的错误是在算法中使用函数对象(`Square`)的名称,而不是函数对象(`Square()`)本身的实例,比如:`std::for_each(myVec.begin(), myVec.end(), Square)`,应该使用:`std::for_each(myVec.begin(), myVec.end(), Square())`。
61 |
62 | ## Lambda函数
63 |
64 | Lambda函数可以就地提供需要的功能,编译器当场就能得到相应的信息,因此具有极佳的优化潜力。Lambda函数可以通过值或引用来接收它们的参数,还可以通过值或引用捕获已定义的变量。
65 |
66 | ```c++
67 | std::vector myVec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
68 | std::for_each(myVec.begin(), myVec.end(), [](int& i){ i= i*i; });
69 | // 1 4 9 16 25 36 49 64 81 100
70 | ```
71 |
72 | > **应该首选Lambda函数**
73 | >
74 | > 如果可调用的功能是简短和可以自解释的,使用Lambda函数最好不过。Lambda函数通常比函数或函数对象更快,而且更容易理解。
75 |
76 | ## 延迟求值
77 |
78 | [延迟求值](https://en.wikipedia.org/wiki/Lazy_evaluation)的情况下,仅在需要时才对表达式求值。该策略与立即求值策略正交。延迟求值通常称为按需调用。
79 |
80 | ## 无锁
81 |
82 | 如果保证了系统范围内的进程无影响,那么非阻塞算法就是无锁的。
83 |
84 | ##未唤醒
85 |
86 | 未唤醒是指,线程由于竞争条件而丢失唤醒通知的情况。
87 |
88 | 如果使用没有使用谓词,可能会发生这种情况。
89 |
90 | ## 数学规律
91 |
92 | 某个集合X上的一个二进制操作(*):
93 |
94 | * 结合律,满足x, y, z中的所有x, y, z的结合律:(x * y) * z = x * (y * z)
95 | * 交换律,满足所有x和y的交换律x * y = y * x
96 |
97 | ## 内存位置
98 |
99 | 内存位置的详解可以参考[cppreference.com](http://en.cppreference.com/w/cpp/language/memory_model)
100 |
101 | * 标量类型的对象(算术类型、指针类型、枚举类型或`std::nullptr_t`。
102 | * 非零长度的最大连续序列。
103 |
104 | ## 内存模型
105 |
106 | 内存模型定义了对象和内存位置之间的关系,特别是处理了以下问题:如果两个线程访问相同的内存位置,会发生什么情况。
107 |
108 | ##修改顺序
109 |
110 | 对特定原子对象M的所有修改,都以特定的顺序进行,这个顺序称为M的修改顺序。因此,线程读取原子对象时,不会看到比线程已经观察到的值更“旧”的值。
111 |
112 | ## Monad(单子)
113 |
114 | Haskell作为一种纯函数语言,只有纯函数。这些纯函数的一个关键特性,当给定相同的参数时,总是返回相同的结果。有了这个[透明参照](https://en.wikipedia.org/wiki/Referential_transparency)的属性,Haskell函数才不会有副作用。因此,Haskell有一个概念上的问题。到处都是有副作用的计算,这些计算可能会失败,可能返回未知数量的结果,或者依赖于环境。为了解决这个概念上的问题,Haskell使用单子并将它们嵌入到纯函数语言中。
115 |
116 | 经典的单子封装:
117 |
118 | * I/O单子:计算输入和输出的结果。
119 | * 可能性单子:可能会返回计算结果的单子。
120 | * 错误单子:计算可能失败。
121 | * 列表单子:计算可以有任意数量的结果。
122 | * 状态单子:基于状态的计算。
123 | * 读者单子:基于环境的计算。
124 |
125 | 单子的概念来自数学中的[范畴理论](https://en.wikipedia.org/wiki/Category_theory),其处理对象之间的映射。单子是抽象的数据类型,将简单的类型转换为丰富的类型。这些丰富类型的值称为一元值。当进入单子,一个值只能由一个函数组合转换成另一个一元值。
126 |
127 | 这种组合尊重了单子的独特结构。因此,当发生错误,错误单子中断它的计算,或重新构建状态单子的状态。
128 |
129 | 一个单子包括三个部分:
130 |
131 | * 类型构造函数:定义简单数据类型,如何成为一元数据类型。
132 | * 函数:
133 | * 恒等函数:在单子中引入一个简单的值。
134 | * 绑定操作符:定义如何将函数应用于一元值,以获得新的一元值。
135 | * 功能规则:
136 | * 恒等函数的左右必须是恒等元素。
137 | * 函数的复合必须遵循结合律。
138 |
139 | 要使错误单子成为类型类单子的实例,错误单子必须支持恒等函数和绑定操作符,这两个函数定义了错误单子应该如何处理计算中的错误。如果使用错误单子,错误处理会在后台完成。
140 |
141 | 单子由两个控制流组成:用于计算结果的显式控制和用于处理特定副作用的隐式控制流。
142 |
143 | 当然,也可以用更少的词来定义单子:“单子只是内函子类中的一个独异点(monoid)。”
144 |
145 | 单子在C++中变得越来越重要。在C++ 17中,添加了[`std::optional`](http://en.cppreference.com/w/cpp/utility/optional) ,这是一种可能性单子。在C++20/23中,可能会从Eric Niebler那里得到扩展future和[范围库]( http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4128.html),二者也都是单子。
146 |
147 | ## 无阻塞
148 |
149 | 如果任何线程的失败或挂起,不会导致另一个线程的失败或挂起,则称为非阻塞。这个定义来自于[《Java并发实践》]( http://jcip.net/)。
150 |
151 | ## 并行性
152 |
153 | 并行性意味着同时执行多个任务。并行性是并发性的一个子集。
154 |
155 | ## 谓词
156 |
157 | 谓词是返回布尔值的可调用单元。如果一个谓词有一个参数,它就称为一元谓词。如果一个谓词有两个参数,就称为二元谓词。
158 |
159 | ## 模式
160 |
161 | “每个模式规则都是一个由三部分组成,表明了特定上下文、问题和解决方案之间的关系。“ —— [Christopher Alexander](https://en.wikipedia.org/wiki/Christopher_Alexander)
162 |
163 | ## RAII
164 |
165 | 资源获取是初始化(RAII),代表C++中的一种流行技术,在这种技术中,资源的获取和释放与对象的生命周期绑定在一起。这意味着对于锁,互斥锁将被锁定在构造函数中,并在析构函数中解锁。这种RAII实现,也称为范围锁定。
166 |
167 | C++中的典型用例有:管理互斥锁生命周期的锁、管理资源(内存)生命周期的智能指针,或者管理元素生命周期的[标准模板库容器](https://en.cppreference.com/w/cpp/container)。
168 |
169 | ## 释放序列
170 |
171 | 原子对象M的释放序列,以释放操作A为首,是M修改顺序中最大的连续子序列,其中第一个操作为A,每个后续操作为:
172 |
173 | * 由执行A操作的线程进行的操作
174 | * 原子的读-改-写操作。
175 |
176 | ## 顺序一致的存储模型
177 |
178 | 顺序一致有两个基本特征:
179 |
180 | 1. 程序的指令是按源代码顺序执行的。
181 | 2. 所有线程上的所有操作都遵循全局顺序。
182 |
183 | ## 序列点
184 |
185 | 序列点定义了程序执行过程中的任何一个结点。在这个点上,可以保证先前评估的所有执行效果,而不影响后续评估的 执行效果。
186 |
187 | ## 伪唤醒
188 |
189 | 伪唤醒是一种条件变量的现象。可能发生的情况是,条件变量的等待组件错误地获取了一个通知。
190 |
191 | ## 线程
192 |
193 | 计算机科学中,执行线程是可由调度器独立管理的最小程序指令序列,调度器通常是操作系统的一部分。线程和进程的实现在不同的操作系统之间是不同的,但是在大多数情况下,线程是进程的一个组件。多个线程可以存放在于一个进程中,并发执行并共享内存等资源,而不同的进程不共享这些资源。特别是,进程中的线程在任何给定时间,共享其可执行代码和变量。想要了解更多信息,可以阅读维基百科关于[线程](https://en.wikipedia.org/wiki/Thread_(computing))的文章。
194 |
195 | ## 全序关系
196 |
197 | 总序是一个二元关系(<=)在某个集合X上表现,其有反对称性、传递性,完全性。
198 |
199 | * 反对称性:如果a <= b并且b <= a,则a == b
200 | * 传递性:如果a <= b, b <= c,则a <= c
201 | * 完全性:a <= b或b <= a
202 |
203 | ## volatile
204 |
205 | volatile通常用于表示可以独立于常规程序流进行更改的对象。例如,这些对象在嵌入式编程中表示一个外部设备(内存映射I/O)。由于这些对象可以独立于常规程序流进行更改,并且其值可以直接写入主内存,因此不会在缓存中进行优化存储。
206 |
207 | ## 无等待
208 |
209 | 当有每个线程都有进程保证不会互相影响时,那么一个非阻塞算法是无等待的。
210 |
211 |
--------------------------------------------------------------------------------
/content/History-Quick-Overview.md:
--------------------------------------------------------------------------------
1 | # C++并行历史概述
2 |
3 | 随着C++11的发布,C++标准添加了多线程和内存模型。这样,标准库有了基本的构建块,比如:原子变量、线程、锁和条件变量。C++11提供了比引用更抽象的构建块,这是未来C++标准(C++20/23)能建立更高抽象的基础。
4 |
5 | 
6 |
7 | 粗略地说,可以将C++并发分为三个演化过程。
8 |
9 | ## C++11和C++14: 铺垫
10 |
11 | C++11引入多线程,包括两个部分:良好的内存模型和标准化的线程接口。C++14为C++的多线程功能增加了读写锁。
12 |
13 | ### 内存模型
14 |
15 | 多线程的基础,是定义良好的内存模型。内存模型需要处理以下几个方面的内容:
16 |
17 | * 原子操作: 不受中断地操作。
18 | * 部分排序运算: 不能重排序的操作序列。
19 | * 操作的可见效果: 保证其他线程可以看到对共享变量的操作。
20 |
21 | C++内存模型的灵感来自于Java。然而,与Java的内存模型不同,C++允许打破顺序一致性的约束(原子操作的默认方式)。
22 |
23 | 顺序一致性提供了两个保证:
24 |
25 | 1. 程序指令按源码顺序执行。
26 | 2. 线程上的所有操作都遵循一个全局顺序。
27 |
28 | 内存模型基于原子数据类型(短原子)的原子操作。
29 |
30 | ### 原子类型
31 |
32 | C++有一组基本的原子数据类型,分别是布尔值、字符、数字和指针的变体。可以使用类模板`std::atomic`来定义原子数据类型。原子类型可以建立同步和排序约束,也适用于非原子类型。
33 |
34 | 标准化线程接口是C++并发的核心。
35 |
36 | ### 多线程
37 |
38 | C++中的多线程由线程、(共享数据的)同步语义、线程本地数据和任务组成。
39 |
40 | ### 线程
41 |
42 | `std::thread`表示一个独立的程序执行单元。执行单元,表示可接受调用的单元。可调用单元可以是函数名、函数对象或Lambda函数。
43 |
44 | 新线程的可执行单元结束时,要么进行等待主线程完成(`t.join()`),要么从主线程中分离出来(`t.detach()`)。如果没有对线程`t`执行`t.join()`或`t.detach()`操作,则线程`t`是可汇入的(joinable)。如果可汇入线程进行销毁时,会在其析构函数中调用`std::terminate`,则程序终止。
45 |
46 | 分离的线程在后台运行,通常称为**守护线程**。
47 |
48 | `std::thread`是一个可变参数模板,它可以接收任意数量的参数。
49 |
50 | #### 共享数据
51 |
52 | 如果多个线程同时使用共享变量,并且该变量是可变的(非const),则需要协调对该变量的访问。同时读写共享变量是一种数据竞争,也是一种未定义的行为。在C++中,可以通过锁(或互斥锁)来协调对共享变量的访问。
53 |
54 | #### 互斥锁
55 |
56 | 互斥锁(互斥量)保证在任何给定时间内,只有一个线程可以访问共享变量。互斥锁锁定/解锁共享变量所属的临界区(C++有5个不同的互斥对象)。即使互斥锁同时共享一个锁,也可以递归地、试探性地、有或没有时间限制地进行锁定。
57 |
58 | #### 锁
59 |
60 | 应该将互斥锁封装在锁中,从而自动释放互斥锁。锁通过将互斥锁的生命周期绑定到自己的生命周期来实现RAII。C++中`std::lock_guard`/`std::scoped_lock`可用于简单场景,`std::unique_lock`/`std::shared_lock`用于高级场景,例如:显式锁定或解锁互斥锁。
61 |
62 | ### 线程本地数据
63 |
64 | 将变量声明为`thread-local`可以确保每个线程都有变量的副本。线程本地数据的生存周期,与线程的生存周期相同。
65 |
66 | ### 条件变量
67 |
68 | 条件变量允许通过消息机制对线程进行同步。一个线程为发送方,而另一个线程为接收方,其中接收方阻塞等待来自发送方的消息。条件变量的典型用例是"生产者-消费者"模式。条件变量可以是发送方,也可以是接收方。正确使用条件变量非常具有挑战性。所以,这样的任务通常有更简单的解决方案。
69 |
70 | ### 任务
71 |
72 | 任务与线程有很多共同之处。虽然显式地创建了一个线程,但任务只是工作的开始。C++运行时会自动处理任务的生存期,比如:`std::async`。
73 |
74 | 任务就像两个通信端点之间的数据通道。支持线程之间的安全通信,当一个端点将数据放入数据通道时,另一个端点将在未来某个时刻获取该值。数据可以是值、异常或通知。除了`std::async`, C++还有`std::promise`和`std::future`,这两个类模板可以对任务有更多的控制。
75 |
76 | ## C++17: 标准模板库算法的并行
77 | 
78 |
79 | C++17的并发性发生了巨大的变化,特别是标准模板库(STL)的并行算法。C++11和C++14只提供了并发性的基础构建块。这些工具适合库或框架开发人员,但不适合应用程序开发人员。C++11和C++14中的多线程,在C++ 17中的并发性面前,相当于汇编语言!
80 |
81 | ### 执行策略
82 |
83 | C++17中,大多数STL算法都有并行实现,这样就可以使用执行策略来调用算法。该策略指定算法是串行执行(`std::execution::seq`)、并行执行(`std::execution::par`),还是与向量化的并行执行(`std::execution::par_unseq`)。
84 |
85 | ### 新算法
86 |
87 | 除了在重载,并行了原始的69种算法,还添加了8种新算法。这些新算法非常适合并行归约、扫描或转换。
88 |
89 | ## 案例研究
90 |
91 | 介绍了内存模型和多线程接口的理论知识之后,会将这些知识应用到一些案例中。
92 |
93 | ### 求向量元素的加和
94 |
95 | 计算一个向量的加和有多种方法。可以串行执行,也可以通过数据共享并发执行,不同的实现方式,性能上有很大的差别。
96 |
97 | ### 单例:线程安全的初始化
98 |
99 | 单例对象的初始化是线程安全的,是共享变量线程安全初始化的经典案例。有许多实现方法可以做到这一点,不过在性能上有一定的差异。
100 |
101 | ### 使用CppMem进行优化
102 |
103 | 我会从一个小程序开始,然后不断地改进它,并用CppMem验证优化过程的每个步骤。 [CppMem](http://svr-pes20-cppmem.cl.cam.ac.uk/cppmem)是一个交互式工具,用于研究小代码段的C++内存模型行为。
104 |
105 | ## C++20/23: 并发的未来
106 |
107 | 
108 |
109 | 对未来的标准预测非常难([Niels Bohr](https://en.wikipedia.org/wiki/Niels_Bohr)),这里描述了C++20/23的并发特性。
110 |
111 | ### Executors
112 |
113 | Executor由一组如何运行可调用单元的规则组成。它们指定执行是否应该在线程、线程池,甚至单线程(无并发)上运行(可调用的)基础构建块上进行。提案[N4734](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4734.pdf)的扩展依赖于扩展的future,也依赖于STL的并行算法,以及C++20/23中新的并发特性,如:门闩和栅栏、协程、事务性内存和任务块(最终都会使用它们)。
114 |
115 | #### std::jthread
116 |
117 | `std::jthread`是`std::thread`的增强版。除了`std::thread`外,`std::jthread`还可以发出中断信号,并自动并入启动的线程。
118 |
119 | #### 原子智能指针
120 |
121 | 智能指针[`std::shared_ptr`](http://en.cppreference.com/w/cpp/memory/shared_ptr)和[`std::weak_ptr`](http://en.cppreference.com/w/cpp/memory/weak_ptr)在并发程序中存在概念问题。它们的本质上是共享的,这就使得状态可变,所以容易出现数据竞争,从而导致未定义的行为。`std::shared_ptr`和`std::weak_ptr`保证引用计数器的递增或递减是一个原子操作。资源只被删除一次,但不能保证对资源访问的原子性。新的原子智能指针`std::atomic>`和`std::atomic>`解决了这个问题。两者都是`std::atomic`的偏特化版本。
122 |
123 | #### 扩展版future
124 |
125 | C++11引入了promise和future,其有很多优点,但也有一个缺点:不能组合成强大的工作流。在C++20/23中,future应该会消弭这个缺点。
126 |
127 | #### 门闩和栅栏
128 |
129 | C++14没有信号量,而信号量是用于限制访问资源的利器。因为C++20/23提出了门闩和屏障,就不用担心没有信号量可用的问题了。可以使用门闩和栅栏在异步点进行等待,直到计数器变为零。门闩和栅栏的区别在于,`std::latch`只能使用一次,而`std::barrier`和`std::flex_barrier`可以使用多次。与`std::barrier`不同,`std::flex_barrier`可以在每次迭代之后调整它的计数器。
130 |
131 | #### 协程
132 |
133 | 协程是可以挂起,并保持执行函数的状态。协程通常在操作系统、事件循环、无限列表或管道中使用,用于实现需要协作才能完成的任务。
134 |
135 | #### 事务内存
136 |
137 | 事务内存基于数据库理论中事务的基本思想。事务是一种操作,它提供了ACID数据库事务的前三个属性:原子性、一致性和隔离性。数据库特有的持久性不适用C++的事务内存。新标准有两种类型的事务内存:同步块和原子块。它们都按总顺序执行的,表现得好像有一个全局锁在保护它们。与同步块相比,原子块不能执行事务不安全的代码。
138 |
139 | #### 任务块
140 |
141 | 任务块在C++中实现了fork-join范式。下图说明了任务块的关键思想:启动任务的fork阶段和同步任务的join阶段。
142 |
143 | 
144 |
145 | ### 模式和最佳实践
146 |
147 | 模式是从实践中记录下来的最佳方式。[Christopher Alexander](https://en.wikipedia.org/wiki/Christopher_Alexander)说,“模式表达了特定环境、问题和解决方案之间的关系“。从更概念化的角度看待并发编程,会得到更多解决问题的方式。与更概念化的并发模式相比,本章提供了面对并发挑战的实用技巧。
148 |
149 | #### 同步
150 |
151 | 数据竞争的必要前提是数据处于共享的、可变状态。同步模式可以归结为两个问题:处理共享和处理可变。
152 |
153 | #### 并行架构
154 |
155 | 并发架构章节中介绍了三种模式。前两种模式是活动对象和监视器对象的同步,以及调度器方法的使用。第三种半同步/半异步模式关注体系结构,并在并发系统中解耦异步和同步(服务)的处理。
156 |
157 | #### 最佳实践
158 |
159 | 并发编程比较复杂,因此通过最佳实践,可以更多的了解多线程和内存模型。
160 |
161 | ###数据结构
162 |
163 | #### 挑战项目
164 |
165 | 编写并发程序本来就很复杂,使用C++11和C++14的特性也是如此。因此,我将详细描述具挑战性的问题。希望用一整章的篇幅来讨论并发编程的挑战,会让你更清楚其中的陷阱。这里有竞争条件、数据竞争和死锁等挑战项目。
166 |
167 | #### 计时库
168 |
169 | 计时库是C++并发工具的重要组成部分。通常,可以让线程在特定的时间内处于休眠状态,或者一直休眠到特定的时间点。计时库包括:时间点、时间段和时钟。
170 |
171 | #### CppMem
172 |
173 | CppMem是一个交互式工具,用于深入了解内存模型。它提供了两项非常有价值的服务:可以验证无锁代码,可以分析无锁代码,并且能得到对代码的鲁棒性有更多的理解。本书会经常使用CppMem。由于CppMem的配置选项和见解非常具有挑战性,也会提供相应章节,以便对CppMem有一些基本的了解。
174 |
175 | #### 术语表
176 |
177 | 术语表对最基本的术语作了简单的解释。
--------------------------------------------------------------------------------
/content/How-you-should-read-the-book.md:
--------------------------------------------------------------------------------
1 | # 如何阅读
2 |
3 | 如果对C++的并发性不是很熟悉,可以从最开始的部分开始,先快速地了解一下。
4 |
5 | 当有了大概的了解,就可以着手处理细节。第一遍阅读可以先跳过内存模型,不过在案例研究章节将之前的理论进行实践,因为需要对内存模型所有理解,所以非常有挑战性。
6 |
7 | "未来:C++20/23"是可选择性阅读章节。我对未来非常好奇,希望你和我一样!
8 |
9 | 最后,为了更好地理解书中的内容,并充分利用这些知识,本书还提供了额外的应用指导。
--------------------------------------------------------------------------------
/content/Pattrns/Best-Practices/9.0-chinese.md:
--------------------------------------------------------------------------------
1 | # 最佳实践
2 |
3 | 本章提供了一组简单的规则,可用于在现代C++中编写良好且快速的并发程序。多线程的并行性和并发性,在C++中算是个比较新的主题,在未来将发现越来越多的最佳实践方式。规则会随着时间推移而发展,所以不要把本章的规则看作一个完整的列表,而是作为一个起点,对于并行STL尤其如此。在更新这本书的时候(2018年12月),C++17的并行算法只是部分可用,所以现在为它定制最佳实践还为时过早。
--------------------------------------------------------------------------------
/content/Pattrns/Best-Practices/9.1-chinese.md:
--------------------------------------------------------------------------------
1 | # 通常情况
2 |
3 | 我们先从一些原子操作和线程操作的最佳实践开始。
4 |
5 | ## 代码评审
6 |
7 | 代码评审应该是专业软件开发过程必备的一部分,尤其是处理并发。并发性本质上非常复杂,需要深思熟虑的分析和经验。
8 |
9 | 为了使评审更有效,请在评审之前将想要讨论的代码发送给评审人员,并声明代码中哪些地方是不可变的。正式评审开始之前,应该给予评审员足够的时间来分析代码。
10 |
11 | 不知道怎么做?举个例子。还记得`std::shared_lock`一章readerWriterLock.cpp中的数据竞争吗?
12 |
13 | ```c++
14 | // readerWriterLock.cpp
15 |
16 | #include
17 | #include