为了账号安全,请及时绑定邮箱和手机立即绑定

并发性:C+11内存模型中的原子和易失性

并发性:C+11内存模型中的原子和易失性

C++
慕桂英4014372 2019-10-20 16:08:44
并发性:C+11内存模型中的原子和易失性在两个不同内核上并发运行线程之间共享一个全局变量。线程写入变量并从变量中读取。对于原子变量,一个线程可以读取一个陈旧的值吗?每个核心的缓存中可能有一个共享变量的值,当一个线程在缓存中写入其副本时,另一个核心上的线程可能从自己的缓存中读取陈旧的值。或者编译器会对其他缓存中的最新值进行强内存排序?c+11标准库具有std:原子支持。这与易失性关键字有何不同?在上述场景中,易失性类型和原子类型的行为会有多大不同?
查看完整描述

3 回答

?
慕神8447489

TA贡献1780条经验 获得超1个赞

首先,volatile并不意味着原子访问。它是为诸如内存映射I/O和信号处理之类的东西而设计的。volatile在一起使用时完全没有必要std::atomic,除非你的平台文件另有规定,volatile对线程之间的原子访问或内存排序没有影响。

如果有一个在线程之间共享的全局变量,如:

std::atomic<int> ai;

然后,可见性和排序约束取决于用于操作的内存排序参数,以及锁、线程和对其他原子变量的访问的同步效果。

在没有任何附加同步的情况下,如果一个线程将一个值写入ai然后,没有任何东西可以保证另一个线程在任何给定的时间段内都会看到该值。该标准规定“在合理的时间内”它应该是可见的,但是任何给定的访问都可能返回一个陈旧的值。

的默认内存顺序。std::memory_order_seq_cst为所有用户提供一个单一的全局总订单。std::memory_order_seq_cst所有变量的操作。这并不意味着您不能得到陈旧的值,但它确实意味着您得到的值决定并由您的操作在这个总顺序中的位置决定。

如果有两个共享变量xy,最初为零,并有一个线程将1写到x另一个写2到y,读取这两个操作的第三个线程可能会看到(0,0)、(1,0)、(0,2)或(1,2),因为操作之间没有排序约束,因此操作可能以全局顺序出现在任何顺序。

如果两个写入都来自同一个线程,则x=1以前y=2读取线程读取y以前x则(0,2)不再是有效的选项,因为y==2意味着先前写到x是可见的。其他3对(0,0)、(1,0)和(1,2)仍然是可能的,这取决于2对与2写的交织方式。

如果使用其他内存顺序,如std::memory_order_relaxedstd::memory_order_acquire这样,约束就会进一步放松,单一的全局排序就不再适用了。如果没有额外的同步,线程甚至不必就两个存储的顺序来分离变量达成一致。

确保您拥有“最新”值的唯一方法是使用读-修改-写入操作,例如exchange()compare_exchange_strong()fetch_add()..读-修改-写入操作有一个附加约束,它们总是对“最新”值进行操作,因此ai.fetch_add(1)由一系列线程进行的操作将返回一个没有重复或空白的值序列。在没有附加约束的情况下,仍然无法保证哪些线程会看到哪些值。

使用原子操作是一个复杂的主题。我建议您阅读大量背景材料,并在使用Atomics编写产品代码之前检查已发布的代码。在大多数情况下,编写使用锁的代码更容易,而且效率也不会明显降低。



查看完整回答
反对 回复 2019-10-21
?
凤凰求蛊

TA贡献1825条经验 获得超4个赞

以下是这两件事的基本概要:

1)易失性关键字:
告诉编译器,这个值可以随时更改,因此它不应该将其缓存在寄存器中。在C中查找旧的“寄存器”关键字。“易失性”基本上是“-”操作符来“注册”的“+”。现代编译器现在做的优化,“寄存器”在默认情况下明确请求,所以你只看到‘易失性’。使用易失性限定符将确保您的处理不会使用陈旧的值,但不会使用更多的值。

2)原子:
原子操作在单个时钟滴答中修改数据,因此任何其他线程都不可能在更新过程中访问数据。它们通常仅限于硬件支持的任何单时钟组装指令;例如+、-和交换2个指针。请注意,这并没有说明不同线程将运行原子指令的顺序,只是它们永远不会并行运行。这就是为什么你有那么多额外的选项来强制订货。



查看完整回答
反对 回复 2019-10-21
  • 3 回答
  • 0 关注
  • 364 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信