C ++ 11 内存模型
如果至少一个操作是修改(也称为存储操作 ),则尝试访问相同存储器位置的不同线程参与数据竞争。这些数据争用导致未定义的行为。为了避免它们,需要防止这些线程同时执行这种冲突操作。 ** ** **
同步原语(互斥,临界区等)可以保护这种访问。C++ 11 中引入的内存模型定义了两种新的可移植方式,用于在多线程环境中同步对内存的访问:原子操作和围栏。
原子操作
现在可以通过使用原子加载和原子存储操作来读取和写入给定的内存位置。为方便起见,这些包含在 std::atomic<t>
模板类中。此类包含 t
类型的值,但这次加载和存储到对象是原子的。
该模板不适用于所有类型。哪些类型可用是特定于实现的,但这通常包括大多数(或所有)可用的整数类型以及指针类型。所以 std::atomic<unsigned>
和 std::atomic<std::vector<foo> *>
应该是可用的,而 std::atomic<std::pair<bool,char>>
很可能不会。
原子操作具有以下属性:
- 所有原子操作都可以从多个线程同时执行,而不会导致未定义的行为。
- 一个原子负荷会看到其任一原子对象用构造的初始值,或经由一些写入它的价值原子商店操作。
- ** 对同一原子对象的原子存储在所有线程中的排序相同。如果某个线程已经看到某个原子存储操作的值,则后续的原子加载操作将看到相同的值或后续原子存储操作所存储的值。
- 原子读 - 修改 - 写操作允许原子加载和原子存储发生,而不需要其他原子存储。例如,可以从多个线程原子地递增计数器,并且不管线程之间的争用如何都不会丢失增量。
- 原子操作接收可选的
std::memory_order
参数,该参数定义操作对其他存储器位置的附加属性。
std::memory_order | 含义 |
---|---|
std::memory_order_relaxed |
没有其他限制 |
std::memory_order_release →std::memory_order_acquire |
如果 load-acquire 看到 store-release 存储的值,那么存储在 store-release 发生之前排序,然后在负载获取后的负载**排序之前 ** |
std::memory_order_consume |
像 memory_order_acquire 但仅适用于依赖负载 |
std::memory_order_acq_rel |
结合了 load-acquire 和 store-release |
std::memory_order_seq_cst |
顺序一致性 |
这些内存顺序标记允许三种不同的内存排序规则: 顺序一致性,放松和释放 - 获取与其兄弟版本消耗。
顺序一致性
如果没有为原子操作指定内存顺序,则顺序默认为顺序一致性。也可以通过使用 std::memory_order_seq_cst
标记操作来明确选择此模式。
使用此顺序,没有内存操作可以跨越原子操作。在原子操作发生之前,所有内存操作都按顺序执行,原子操作发生在所有在其后排序的内存操作之前。这种模式可能是最容易推理的模式,但它也会导致对性能的最大惩罚。它还会阻止所有可能尝试重新排序原子操作之外的操作的编译器优化。
轻松的订购
与顺序一致性相反的是宽松的内存排序。它是使用 std::memory_order_relaxed
标签选择的。轻松的原子操作不会对其他内存操作施加任何限制。剩下的唯一影响是操作本身仍然是原子的。
发布 - 获取订购
一个原子商店操作可标记为 std::memory_order_release
和原子负荷操作可以 std::memory_order_acquire
被标记。第一个操作称为 *(原子)存储释放,*而第二个操作称为 (原子)load-acquire 。
当 load-acquire 看到由 store-release 写入的值时,会发生以下情况:所有在 store-release 之前排序的存储操作变得可见( 在之前发生 )在 load-acquire 之后排序的加载操作。
原子读 - 修改 - 写操作也可以接收累积标签 std::memory_order_acq_rel
。这使得原子负载的操作的一部分的原子负载获取而原子商店部分成为原子商店释放。
在原子存储释放操作之后,不允许编译器移动存储操作。在原子加载 - 获取 (或加载 - 消耗 ) 之前,也不允许移动加载操作。
另请注意,没有原子加载释放或原子存储获取。尝试创建此类操作使他们放松操作。
发布 - 消费订购
这种组合类似于 release-acquire ,但这次原子载荷用 std::memory_order_consume
标记并成为 (原子)负载消耗操作。此模式是相同的释放 -获得唯一的区别是,之后测序负载操作中的负载消耗只有这些取决于由装入的值负载消耗是有序的。
栅栏
Fences 还允许在线程之间排序内存操作。围栏可以是释放围栏或获取围栏。
如果释放围栏在获取围栅之前发生,则在释放围栏之前排序的储存对于在获取围栏之后排序的载荷可见。为了保证释放围栏在获取围栏之前发生,可以使用其他同步原语,包括放松原子操作。