导入
契约(导读)
本课程简要介绍了C++内存模型的起点,内容包括程序员与系统之间的约定。
多线程的基础是一个明确界定的内存模型。从读者的角度来看,它包含两个方面:一方面,其复杂性极为庞大,常常与我们的直觉相悖;另一方面,它有助于我们更深入地理解多线程所面临的挑战。首先,我想为你提供一个思维模型。需要说明的是,C++内存模型定义了一种契约。
## 契约 这种契约存在于程序员与系统之间。系统包括生成机器代码的编译器、执行机器代码的处理器,以及存储程序状态的各种缓存。 理想情况下,结果是一个针对硬件平台充分优化的*明确界定*的可执行程序。严格来说,这并非单一契约,而是一组细粒度的契约;即程序员需要遵守的规则越弱,系统生成高度优化可执行程序的潜力就越大。 有一个经验法则:契约越强,系统在生成优化代码时的自由度就越少。遗憾的是,反过来并不成立。当程序员使用极其宽松的契约或内存模型时,系统拥有大量优化选择。其后果是,只有极少数全球公认的专家才能驾驭这样的程序,而你我大概率不在其中。粗略地说,C++11中存在三个契约层级。 <img src="http://network.jancsitech.net:9000/pageflex-ai/images/general/202509/1758376571810153806.png" alt="Overview.png" width="673" height="712" /> ### 第一级 在C++11之前,只存在一种契约。C++语言规范并未包含多线程或[原子操作](https://www.educative.io/courses/modern-cpp-concurrency-in-practice-get-the-most-out-of-any-machine/strong-memory-model)的概念。系统仅了解单一控制流,因此优化空间非常有限。系统的重点在于为程序员保证:程序的观察行为与源代码中指令的顺序一致。当然,这意味着当时并不存在内存模型,而是采用“[序列点](https://www.educative.io/courses/modern-cpp-concurrency-in-practice-get-the-most-out-of-any-machine/glossary)”这一概念。序列点是程序中的某些位置,在这些位置上,所有前置指令的效果都必须被观察到。函数的开始或结束处就是序列点。当你调用一个带有两个参数的函数时,C++标准不保证哪个参数会先被求值,因此行为未定义。原因很简单:逗号运算符不是序列点,这一点在C++中也不会改变。第二级
自C++11起,一切发生了变化。C++11是首个正式支持多线程的标准。受到Java内存模型的深刻影响,C++内存模型确保了线程行为的明确性。然而,正如一贯风格,C++内存模型走得更远。程序员在处理共享变量时必须遵循一些规则,才能获得一个明确界定的程序。只要存在至少一个数据竞争,程序的行为就是未定义的。如前所述,如果你的线程共享可变数据,就必须警惕数据竞争。相比线程或条件变量,任务的使用要简单得多。
### 第三级 引入原子操作后,我们进入了专家领域。随着C++内存模型的进一步弱化,这一点将愈发明显。当我们使用原子操作时,通常会谈到[无锁编程](https://www.educative.io/courses/modern-cpp-concurrency-in-practice-get-the-most-out-of-any-machine/glossary)。本小节中提到的弱规则与强规则,实际上正是:[顺序一致性](https://www.educative.io/courses/modern-cpp-concurrency-in-practice-get-the-most-out-of-any-machine/glossary)被称为强内存模型,而[宽松语义](https://www.educative.io/courses/modern-cpp-concurrency-in-practice-get-the-most-out-of-any-machine/relaxed-semantic)则被称为弱内存模型。