C++ Memory Model

原文链接:

https://www.modernescpp.com/index.php/c-memory-model/

从c++11开始,C++有了它自己的内存模型。C++的内存模型是多线程编程的基础,如果没有内存模型,C++多线程编程便不能说自己是完善的。

C++内存模型包括两方面内容。一方面,C++内存模型的巨大复杂性常常是反直觉的,另外一方面,它帮助我们能够更加深入了解多线程编程。

约定

一开始,C++内存模型便定义了约定。这个约定是建立在编程人员和系统之间的。系统包括编译器和处理器,编译器帮助程序编译成汇编指令,处理器将负责执行这些指令,同时利用缓存来存储程序的状态。约定要求程序员写的程序能够在遵守这些规则的情况下,完美发挥系统的性能。在完美的情况下,一个好的程序能够被最大程度的优化。确切地说,不只是一条规则,而且是一系列的规定。换种说法,程序员必须遵循的规则越弱,系统就越有可能生成高度优化的可执行文件。一般来说,约定要求越高,系统进行优化的空间越小。但不幸的是,反过来却不行。如果你写的程序的约束非常少的话,估计只有哪些世界顶级专家才能理解了。

C++11的约定分成三层

 在C++11之前只有一种约定。C++是感知不到是原子操作还是多线程。系统只能感知一种控制流,所以只能有有限的空间进行可执行程序的优化。系统认为至关重要的是需要让程序员保持程序是按照源代码顺序执行的错觉。当然,这种情况下是没有内存模型的。只有序列点的概念。序列点是程序中的点,在这些点上之前所有指令的效果必须是可观察到的。函数执行的开始和结束是被保证的序列点。但根据C++标准,拥有两参的函数,哪个参数会首先被初始化是不确定的。所以函数的行为是不确定的。原因很简单,逗号不是序列点,这一点在C++11里面也没有改变。

C++11是C++历史上第一个开始重视多线程的版本。原因就是C++的内存模型的重要性。JAVA的内容模型的成功激励了C++相关的工作,同时C++在此基础之上更进了一步。这部分内容是后面一系列文章的主题。所以如果程序员想要开发涉及共享变量的代码,就需要遵守一系列的规则。一旦有数据竞争,程序将会出现一些不可知情况。所以,你需要时刻注意到程序中的线程之间是否有数据竞争的情况。所以任务将会比线程和条件变量更加好用。

关于atomics,这是专家才会涉及的领域。atomics其实会变的更加清晰,因为内存模型的规则这这里变的更加弱化了。关于atomics,我们会经常谈论到无锁编程。接下来,弱或者强规则的模型都会涉及。

约定的实质内容

程序员与系统之间涉及的约定包括一下三部分:

原子操作(atomic operations):操作不能被打断

操作序列(the partial order of opearations):操作的顺序不会被改变

操作的结果可见(visible effects of operations):保证操作或者共享变量在其他线程中也是可见的

约定的基础是操作是原子的。这些操作有两个特性。在程序执行的时候是原子的、同步的、有序的。

这些同步性和有序性的要求对非原子操作也是一样的。也就是说,原子操作肯定是原子性的,但你也可以根据需要对非原子操作来定制同步和顺序的约束。

回到主题

内存模型约束越弱,我们可操作也就越多。

系统可优化的空间越多

程序的控制流的数量呈指数增长

专家的领域

违反直觉

微优化区域

想要多线程编程,我们得成为专家。如果想涉及atomic,我们得进入下一个专家层次。你想知道当我们涉及到acquire-release 或者 relaxed semantic,会发生什么吗?那我们将又开始进入更深的层次。

 

posted @ 2023-11-14 15:14  panda顾  阅读(32)  评论(0)    收藏  举报