首先,你必须学会像语言律师一样思考。
C++规范没有引用任何特定的编译器、操作系统或CPU。它引用了一个抽象的机器,它是对实际系统的概括。在语言律师界,程序员的工作是为抽象机器编写代码;编译器的任务是在具体的机器上实现代码。通过严格按照规范进行编码,您可以确定,无论是今天还是50年后,您的代码都可以在使用兼容C++编译器的任何系统上编译和运行,而无需修改。
C++98/C++03规范中的抽象机器基本上是单线程的。因此,不可能编写与规范相关的“完全可移植”的多线程C++代码。该规范甚至没有说明内存加载和存储的原子性或加载和存储可能发生的顺序,更不用说互斥锁之类的事情。
当然,您可以在实践中为特定的具体系统(如pthreads或Windows)编写多线程代码。但是,没有标准的方法为C++98/C++03编写多线程代码。
C++11中的抽象机器是多线程设计的。它还有一个定义明确的记忆模型;也就是说,它说明编译器在访问内存时可以做什么,也可以不做什么。
考虑以下示例,其中两个线程同时访问一对全局变量:
Global
int x, y;
Thread 1 Thread 2
x = 17; cout << y << " ";
y = 37; cout << x << endl;
线程2可能输出什么?
在C++98/C++03下,这甚至不是未定义的行为;这个问题本身是没有意义的,因为标准没有考虑任何所谓的“线程”。
在C++11下,结果是Undefined Behavior,因为加载和存储一般不需要是原子的。这可能看起来并没有多大的改善。。。就其本身而言,它不是。
但使用C++11,您可以编写以下内容:
Global
atomic<int> x, y;
Thread 1 Thread 2
x.store(17); cout << y.load() << " ";
y.store(37); cout << x.load() << endl;
现在事情变得有趣多了。首先,定义了这里的行为。线程2现在可以打印0 0(如果它在线程1之前运行)、37 17(如果它运行在线程1之后)或0 17(如果在线程1分配给x之后但在分配给y之前运行)。
它无法打印的是37 0,因为C++11中原子加载/存储的默认模式是强制执行顺序一致性。这意味着所有加载和存储都必须“好像”按照您在每个线程中编写它们的顺序发生,而线程之间的操作可以按照系统的喜好进行交错。因此,atomics的默认行为为加载和存储提供了原子性和排序。
现在,在现代CPU上,确保顺序一致性可能代价高昂。特别是,编译器很可能会在每次访问之间释放出全面的内存障碍。但如果您的算法能够容忍无序加载和存储;即,如果它需要原子性但不需要排序;即,如果它可以容忍37 0作为该程序的输出,那么您可以编写:
Global
atomic<int> x, y;
Thread 1 Thread 2
x.store(17,memory_order_relaxed); cout << y.load(memory_order_relaxed) << " ";
y.store(37,memory_order_relaxed); cout << x.load(memory_order_relaxed) << endl;
CPU越现代化,越有可能比前面的示例更快。
最后,如果您只需要保持特定的加载和存储顺序,可以编写:
Global
atomic<int> x, y;
Thread 1 Thread 2
x.store(17,memory_order_release); cout << y.load(memory_order_acquire) << " ";
y.store(37,memory_order_release); cout << x.load(memory_order_acquire) << endl;
这将我们带回到已订购的装载和存储——因此37 0不再是一种可能的输出——但它以最小的开销实现了这一点。(在这个微不足道的例子中,结果与全面的顺序一致性是一样的;在一个更大的程序中,则不会。)
当然,如果您希望看到的输出只有0 0或37 17,那么只需在原始代码周围包装一个互斥锁即可。但如果你读过这篇文章,我打赌你已经知道它是如何工作的,而且这个答案已经比我预期的要长:-)。
所以,底线。互斥非常好,C++11对它们进行了标准化。但有时出于性能原因,您需要较低级别的原语(例如,经典的双重检查锁定模式)。新标准提供了高级小工具,如互斥锁和条件变量,还提供了低级小工具,如原子类型和各种类型的内存屏障。因此,现在您可以完全使用标准指定的语言编写复杂、高性能的并发例程,并且可以确定您的代码将在今天和明天的系统上编译和运行不变。
尽管坦率地说,除非你是一名专家,并且正在处理一些严重的低级代码,否则你可能应该坚持互斥锁和条件变量。这就是我打算做的。
有关此内容的更多信息,请参阅此博客文章。