Concurrency and multithreading in C++
C++11 开始原生支持多线程。为了理解 STL 设计取舍的原因,了解发展历史是很有必要的。
History of multithreading in C++
C++98 缺少一些基础设施:很多语言要素设置的是串行计算,同时缺少内存模型的定义。
不过还是有很多编译器厂商扩展了编译器来支持多线程。另外,有一些通用的多线程库(比如 Boost)或者类库自身包含多线程库(比如 MFC,ACE)。
Concurrency support in the C++11 standard
C++11 定义了内存模型,区分不同线程,还新增了类来管理线程,保护共享数据,多线程同步,底层原子操作。STL 原生支持多线程。同时,很多C++11 的特性能简化多线程的代码。
More support for concurrency and parallelism in C++14 and C++17
C++14 为保护共享数据提供新的互斥类型,C++17 新增一套为新手设计的并行算法。同时,有许多其他改进,进一步简化多线程代码。
技术规范(Technical Specification
)额外有一些扩展,特别是有关线程间的同步。
C++规定了原子操作的语义,那么可以很容易写出跨平台的高效的代码。
Efficiency in the C++ Thread Library
大部分开发者挑选C++类库(包括这里的 STL 多线程库)追求的是性能。开发一个功能,可以使用高级设施,也可以直接使用底层的设施,两者之间可能有性能差异,这称为抽象开销(abstraction penalty
)。追求极致性能的话,理解两者的差异很重要。
C++委员会的人当然明白这一点,所以设计之初的目的就是直接使用底层设施的收益没有或者非常低,也就是说,高级设施的实现是非常高效的。
C++委员会还确保能够直接提供底层设施,使得那些追求极致性能、喜欢接近底层的开发者也能使用标准库。新增的内存模型和原子操作可以直接控制每一个比特/字节、线程间的同步、数据修改的可见性。此外,使用标准库而不是平台特定的API,使得代码更容易移植和维护。
C++也提供了高层的抽象和设施,可以更容易写多线程代码且不容易出错。有的时候可能会有性能损失,毕竟有更多的代码需要运行,但是这个开销非常小,不比手写相同的实现开销更大。
有的时候,高层抽象提供了一些不需要的功能。大部分时候不用就没有额外开销,不过偶尔会带来额外开销。这时你可以用底层设施自己按需实现。对于大部分场景,手工实现带来的复杂度且更容易出错,超过了带来的一点点性能提升。即使性能分析得到瓶颈是在 STL,根本原因也可能是应用自身设计的太差了。比如太多线程竞争一个互斥变量,那么性能会很差。试图优化互斥变量实现提升一点性能是得不偿失的,更应该合理设计程序减少竞争。第八章会覆盖这些主题。
如果 STL 没有提供某些设施,那么不得不使用平台依赖的设施。
Platform-specific facilities
STL 提供了很多并发和多线程的工具,但是平台提供的工具往往更多。比如有的平台实现了native_handle
那么可以直接操作平台特定的API。不过这超出了本书的范围(STL的范围)。