C++11引入了标准化的内存模型,但这到底意味着什么?它会对C++程序设计产生什么影响?
这篇文章(作者是加文·克拉克,他引用了赫伯·萨特的话)说
内存模型意味着C++代码
现在有了一个标准化的图书馆
不管是谁做的编译器
它在什么平台上运行。
有一个标准的方法来控制
不同的线程与
处理器的内存“当你谈论分裂时
[代码]跨不同的核心
在标准中,我们讨论的是
记忆模型。我们将
在不破坏系统的情况下对其进行优化
以下假设是人们要去的地方
在代码中,“萨特说
嗯,我可以在网上记住这段和类似的段落(因为我从出生起就有了自己的记忆模式:p),甚至可以把它作为别人提问的答案,但老实说,我并不完全理解这一点
C++程序员以前就已经开发过多线程应用程序,那么POSIX线程、Windows线程或C++11线程又有什么关系呢?有什么好处?我想了解一些低级的细节
我还觉得C++11内存模型与C++11多线程支持有某种关联,因为我经常看到这两种支持同时存在。如果是,具体情况如何?为什么它们应该有关联
由于我不知道多线程的内部结构是如何工作的,也不知道内存模型通常意味着什么,请帮助我理解这些概念。:-)
首先,你必须学会像语言律师一样思考
C++规范不引用任何特定的编译器、操作系统或CPU。它引用了一个抽象机器,它是实际系统的泛化。在语言律师界,程序员的工作是为抽象机器编写代码;编译器的工作是在一台具体的机器上实现该代码。通过严格地编码,可以确信代码在没有任何C++兼容编译器的系统上编译和运行,无论是现在还是50年。
C++98/C++03规范中的抽象机器基本上是单线程的。因此,不可能编写多线程C++代码,即:“完全便携”;关于规范,规范甚至没有说明内存加载和存储的原子性,或者加载和存储的顺序,更不用说互斥体之类的事情了
当然,您可以在实践中为特定的具体系统(如pthreads或Windows)编写多线程代码。但是没有标准的方法为C++98/C++03编写多线程代码
C++11中的抽象机设计为多线程。它还有一个定义良好的内存模型;也就是说,它说明了编译器在访问内存时可以做什么,也可以不做什么
考虑以下示例,其中一对全局变量由两个线程并发访问:
全局
int x,y;
螺纹1螺纹2
x=17;库特<&书信电报;y<<&引用&“;;
y=37;库特<&书信电报;x<&书信电报;endl;
线程2的输出可能是什么
在C++98/C++03下,这甚至不是未定义的行为;这个问题本身是毫无意义的,因为该标准没有考虑任何被称为“a”的东西;线程"
在C++11下,结果是未定义的行为,因为加载和存储通常不需要是原子的。这似乎不是一个很大的进步。。。就其本身而言,它不是
但使用C++11,您可以编写以下代码:
全局
原子<;int>;x、 y;
螺纹1螺纹2
x、 商店(17);库特<&书信电报;y、 加载()<<&引用&“;;
y、 商店(37);库特<&书信电报;x、 加载()<&书信电报;endl;
现在事情变得有趣多了。首先,这里的行为是定义的。线程2现在可以打印0 0(如果它在线程1之前运行)、37 17(如果它在线程1之后运行)或0 17(如果它在线程1分配给x之后但分配给y之前运行)
它无法打印的是37 0,因为C++11中原子加载/存储的默认模式是强制执行顺序一致性。这只意味着所有装载和存储必须是;仿佛;它们是按照您在每个线程中编写它们的顺序发生的,而线程之间的操作可以根据系统的喜好进行交错。因此原子的默认行为为加载和存储提供了原子性和排序
现在,在现代CPU上,确保顺序一致性可能代价高昂。特别是,编译器可能会在这里的每次访问之间发出完全的内存屏障。但是如果你的算法能够容忍无序的加载和存储;i、 e.如果需要原子性但不需要排序;i、 例如,如果它可以容忍37 0作为此程序的输出,则可以编写以下代码:
全局
原子<;int>;x、 y;
螺纹1螺纹2
x、 存储(17,内存\u顺序\u松弛);库特<&书信电报;y、 加载(内存\u顺序\u松弛)<<&引用&“;;
y、 存储(37,内存\u顺序\u松弛);库特<&书信电报;x、 加载(内存\u顺序\u松弛)<&书信电报;endl;
CPU越是现代化,就越有可能比前面的示例更快
最后,如果您只需要保持特定的加载和存储有序,您可以编写:
全局
原子<;int>;x、 y;
螺纹1螺纹2
x、 存储(17,存储器、命令和释放);库特<&书信电报;y、 加载(内存\u顺序\u获取)<<&引用&“;;
y、 存储(37,存储器\命令\释放);库特<&书信电报;x、 加载(内存\u顺序\u获取)<&书信电报;endl;
这让我们回到有序加载和存储–因此37 0不再是一个可能的输出–但这样做的开销最小。(在这个简单的例子中,结果与完整的顺序一致性是一样的;在一个更大的程序中,结果就不一样了。)
当然,如果您只想看到0 0或37 17输出,您可以在原始代码周围包装一个互斥体。但是如果你已经读了这么多,我打赌你已经知道这是怎么回事了,这个答案已经比我想的要长了:-)
所以,底线是。互斥锁非常好,C++11将其标准化。但有时出于性能原因,您需要较低级别的原语(例如,经典的双重检查锁定模式)。新标准提供了互斥体和条件变量等高级小工具,还提供了原子类型和各种类型的内存屏障等低级小工具。因此,现在您可以完全用标准指定的语言编写复杂、高性能的并发例程,并且您可以确定您的代码将在今天和明天的系统上编译和运行,不会发生任何变化
尽管坦率地说,除非您是一名专家并且正在处理一些严重的低级代码,否则您可能应该坚持使用互斥体和条件变量。这就是我打算做的
有关这方面的更多信息,请参阅本文