其他线程会在合理的时间内看到写入`volatile`字大小的变量吗?

时间:2015-11-30 16:44:37

标签: c++ multithreading c++03 lock-free

在询问more specific problem时,我发现这是人们不确定的核心问题。

可以做出以下假设:

  • CPU确实使用缓存一致性协议,如MESI(F)(例如:x86 / x86_64和ARMv7mp)
  • 假设
  • 变量具有由处理器原子写入/读取的大小(对齐和本机字大小)
  • 变量声明为volatile

问题是:

  • 如果我在一个线程中写入变量,其他线程是否会看到更改?
  • 其他线程看到更改的时间范围的数量级是多少?
  • 您是否了解缓存一致性不足以确保跨CPU /跨核可见性的架构?

问题不是:

  • 使用这样的变量是否安全?
  • 关于重新排序的问题
  • 关于C ++ 11 atomics

这可能被视为In C/C++, are volatile variables guaranteed to have eventually consistent semantics betwen threads?和其他类似问题的重复,但我认为其中没有一个对目标架构有明确的要求,导致对不同假设的许多混淆。

5 个答案:

答案 0 :(得分:2)

  

您是否知道缓存一致性不足以确保跨cpu /跨核可见性的架构?

我不知道任何具有多个内核的处理器存在缓存一致性问题。有人可能会在多处理器板中使用错误类型的处理器,例如具有英特尔处理器的英特尔处理器调用外部QPI禁用,但这会导致各种问题。

关于英特尔QPI的Wiki文章以及启用或禁用它的处理器:

http://en.wikipedia.org/wiki/Intel_QuickPath_Interconnect

答案 1 :(得分:1)

  

如果我在一个线程中写入变量,其他线程是否会看到更改?

无法保证。如果你认为有,请告诉我你在哪里找到它。

  

其他线程看到更改的时间范围的数量级是多少?

永远不会。无法保证。

  

您是否知道缓存一致性不足以确保跨cpu /跨核可见性的架构?

这是一个不连贯的问题,因为您正在讨论必须编译为汇编代码的C ++代码中的操作。即使您有适用于汇编代码的硬件保证,也不能保证这些保证“通过”到C ++代码。

但就问题的答案而言,答案是肯定的。在真实平台中存在已发布的写入,读取预取和其他类型的缓存(例如编译器对寄存器执行的操作)。

答案 2 :(得分:0)

我会说不,没有保证。存在使用多个独立计算机的实现,其中共享数据必须通过计算机之间的(通常非常快速的)连接来传输。在这种情况下,您只会在需要时尝试传输数据。例如,这可能由互斥体和标准原子函数触发,但希望不是由存储到任意本地内存中,也可能不是由存储到易失性存储器中。

我可能错了,但你必须证明我错了。

答案 3 :(得分:0)

现在假设x86 / 64:

  

如果我在一个线程中写入变量,其他线程是否会看到更改?

是。假设您使用的是现代且不太老/错误的编译器。

  

其他线程看到更改的时间范围的数量级是多少?

这取决于你的测量方式。 基本上,这将是在相同NUMA节点上的存储器等待时间= 200个周期。在双节点框上的另一个节点上关于double。较大的盒子可能会有所不同。 如果你的写入相对于时间点测量重新排序,你可以得到+/- 50个周期。

几年前我测量了这个,在3GHz盒子上测量了60-70ns,在另一个节点上翻了一倍。

  

您是否知道缓存一致性不足以确保跨cpu /跨核可见性的架构?

我认为缓存一致性的含义是可见性。话虽如此,我不确定Sun风险机器是否具有相同的缓存一致性,并且放宽了内存模型,就像x86一样,因此我对它们进行了非常仔细的测试。具体来说,您可能需要添加内存释放障碍以强制刷新内存写入。

答案 4 :(得分:0)

鉴于您所描述的假设,无法保证在一个线程中写入volatile变量将在另一个线程中被“看到”。

鉴于此,您的第二个问题(关于时间范围)不适用。

对于(多处理器)PowerPC体系结构,缓存一致性不足以确保volatile变量的跨核可见性。需要执行明确的指令以确保刷新状态(并使其在多个处理器及其缓存中可见)。

实际上,在需要执行此类指令的体系结构上,数据同步原语(互斥体,信号量,关键部分等)的实现确实 - 除其他外 - 使用这些指令。

更广泛地说,C ++中的volatile关键字根本与多线程无关,更不用说与跨缓存一致性有关。在给定的执行线程中,volatile转换为需要对变量的提取和写入等内容进行编译(这会影响优化)。它不会转化为有关在执行线程之间完成提取或写入的顺序或同步的任何要求 - 这些要求对于高速缓存一致性是必需的。

从理论上讲,可以实现编译器来提供这样的保证。我还没有看到有关这样做的任何信息 - 这并不奇怪,因为提供这样的保证会通过强制线程之间的同步严重影响多线程代码的性能 - 即使程序员没有使用同步(互斥等)在他们的代码中。

类似地,主机平台也可以在理论上提供volatile变量的保证 - 即使正在执行的指令没有特别要求它们。同样,这会降低这些平台上多线程程序(包括现代操作系统)的性能。它还会通过强制处理器相互等待来影响(或否定)有助于提高现代处理器性能的各种功能的好处,例如流水线操作。

如果作为C ++开发人员(不同于编写利用您的特定编译器或主机平台提供的特定功能的代码的人),您希望在一个线程中编写的变量能够被另一个线程一致地读取,那么就不要打扰volatile。在线程之间执行同步 - 当它们需要同时访问同一个变量时 - 使用提供的技术 - 例如互斥锁。并遵循使用这些技术的常用指南(例如,谨慎使用互斥锁并最小化它们的持有时间,尽可能多地在线程中执行,而无需访问线程之间共享的变量)。