原文链接:Multithreading in Modern C++

首先感谢作者的分享,然后本文是要用 Google 翻译加上个人的理解进行翻译的,部分内容可能不正确,仅供参考,推荐阅读原文


伴随着新的 C++ 11 标准出现,C++ 首次面临多核体系结构的挑战。2011 年发布的标准定义了在存在多个线程的情况下 C++ 程序的行为方式。C++ 11 多线程的能力由两个组件组成。一个是定义的内存模型,另一个是标准化的线程接口。

定义明确的内存模型

定义的内存模型是必要的基础,以便多线程编程在 C++ 中有意义。因此,内存模型必须给出以下问题的答案。

  1. 什么是原子操作?
  2. 确保怎样的操作顺序?
  3. 什么时候可以看到操作对内存的影响?

对于第 1 个问题:原子操作是遵循数据库理论中著名的 ACID 惯用语的前三个字母的操作。原子操作是原子的(A),从一个一致性(C)状态转到下一个一致性状态,并且是独立执行的(I)。这意味着没有其他线程可以观察到原子操作的中间状态。增量 atomVar++ 很好地显示了原子操作的一致性和隔离性。如果 atomVar 是原子变量,则 atomVar 只能具有旧值或新值。变量 atomVar 的一致性在于,它仅从一种状态更改为另一种状态,并且是隔离的,另一个线程无法观察到任何中间值。

对于第 2 个问题:将程序转换为汇编程序指令的编译器和执行汇编程序指令的处理器都可以重新安排操作。通常,这是出于性能原因。另外,各种存储(缓存)层都可能以延迟的方式提供操作结果

对于第 3 个问题:由于一个线程很有可能比另一个线程晚对一个变量执行操作,因此线程必须遵守某些规则。

标准化线程接口

C ++ 11中的标准化线程接口由以下组件组成

  1. 线程
  2. 任务
  3. 线程本地数据
  4. 条件变量

线程是多线程编程的基本构建块。它们自主地执行工作,通过参数进行参数化,并通过共享变量与其他线程进行交互。

任务是一个相对现代的概念。任务由两个组件组成,这两个组件通过通信通道连接。一个组件作为通道的端点产生结果,而另一端点消耗它。生产者称为 Promise,消费者称为 Future

线程本地数据从名称中很容易猜到是明确属于一个线程的数据。

条件变量使其能够实现生产者/消费者工作流程。消费者等待生产者的通知,以便他可以继续工作。

C++ 17 和 C++ 20 将带来什么?

下一个 C++ 标准计划在 2017 年和 2020 年发布。C++ 17 和 C++ 20 将围绕现有标准的多线程功能进行许多扩展。因为现有功能非常基础。这些更改将可能包含以下三个有趣的功能:

  1. Latches and barriers
  2. 事务内存(Transactional memory)
  3. 自动并行化或矢量化标准模板库(STL)的算法

Latches and barriers 类似于信号量

事务内存是将 ACID 的概念(同样仅前三个字母)应用于代码的想法。这意味着该代码被标注为事务内存,并且在不与其他线程同步的情况下乐观地执行了该代码。在事务结束时,如果初始条件仍然有效,则仅发布结果。如果不是,则结果的输出将被拒绝,然后再次执行。虽然临界区始终由互斥锁锁定,但事务未锁定,但结果可能会被丢弃。临界区是一段代码,最多一次允许进入一个线程。

并行化算法在多个线程上将其操作分布在其容器上时,并行化算法可在单个步骤中对其容器的多个元素执行其操作。