C ++线程,共享数据

时间:2020-03-06 14:34:17  来源:igfitidea点击:

我有一个正在运行2个线程的应用程序...是否有任何证明,当我从一个线程更改全局变量时,另一个会注意到此更改吗?
我没有任何同步或者互斥系统...但是此代码应该一直工作(想象一下名为dataUpdated的全局布尔):

线程1:

while(1) {
    if (dataUpdated)
        updateScreen();
    doSomethingElse();
}

线程2:

while(1) {
    if (doSomething())
        dataUpdated = TRUE;
}

像gcc这样的编译器是否以不检查全局值的方式优化该代码,而仅在编译时考虑它的值(因为它永远不会在同一时间被改变)?

PS:对于类似游戏的应用程序来说,在写入值的过程中是否进行读取并不重要……重要的是更改被另一个线程注意到。

解决方案

使用volatile关键字向编译器提示该值可以随时更改。

volatile int myInteger;

不,不确定。如果将变量声明为volatile,则编译器应该生成始终在读取时从内存中加载变量的代码。

解决方案将使用100%CPU,还有其他问题。 Google表示"条件变量"。

克里斯·杰斯特·杨指出:

This only work under Java 1.5+'s memory model. The C++ standard does not address threading, and volatile does not guarantee memory coherency between processors. You do need a memory barrier for this

如此,唯一的正确答案就是实施同步系统,对吗?

这是一个使用提升条件变量的示例:

bool _updated=false;
boost::mutex _access;
boost::condition _condition;

bool updated()
{
  return _updated;
}

void thread1()
{
  boost::mutex::scoped_lock lock(_access);
  while (true)
  {
    boost::xtime xt;
    boost::xtime_get(&xt, boost::TIME_UTC);
    // note that the second parameter to timed_wait is a predicate function that is called - not the address of a variable to check
    if (_condition.timed_wait(lock, &updated, xt))
      updateScreen();
    doSomethingElse();
  }
}

void thread2()
{
  while(true)
  {
    if (doSomething())
      _updated=true;
  }
}

如果范围是正确的(" extern",global等),则将注意到更改。问题是什么时候?并且以什么顺序?

问题在于,作为性能优化,编译器可以并且经常将对逻辑进行重新排序以填充其所有并发管道。

在特定示例中并没有真正显示它,因为赋值周围没有其他指令,但是可以想象在bool赋值之后声明的函数在赋值之前执行。

在Wikipedia上签出管道危害或者在Google上搜索"编译器指令重新排序"

使用锁。始终使用锁来访问共享数据。将变量标记为volatile将阻止编译器优化对内存的读取,但不会阻止其他问题,例如内存重新排序。如果没有锁,则不能保证在doScreen()函数中可见在doSomething()中写入的内存。

唯一安全的方法是使用内存屏障,例如显式或者隐式使用Interlocked *函数。

正如其他人所说的那样," volatile"关键字是朋友。 :-)

当我们在gcc中禁用所有优化选项时,我们很可能会发现代码可以正常工作。在这种情况下(我相信),它会将所有内容都视为易失性,因此每次操作都会在内存中访问变量。

启用任何类型的优化后,编译器将尝试使用保存在寄存器中的本地副本。根据功能,这可能意味着我们只能间歇地看到变量的变化,或者更糟的是,永远不会看到变化。

使用关键字" volatile"向编译器指示该变量的内容可以随时更改,并且不应使用本地缓存的副本。

综上所述,通过使用信号量或者条件变量,我们可能会发现更好的结果(由Jeff暗示)。

这是对该主题的合理介绍。

是的。不会吧

首先,正如其他人提到的那样,我们需要使dataUpdated易变;否则,编译器可以自由地将其读入循环之外(取决于它是否可以看到doSomethingElse没有触及它)。

其次,根据处理器和订购需求,我们可能需要内存障碍。 volatile足以确保其他处理器最终看到更改,但不足以确保更改将按执行顺序显示。示例只有一个标志,因此它并没有真正显示出这种现象。如果需要并使用内存屏障,则不再需要使用volatile

易失性被认为是有害的,Linux内核内存屏障是潜在问题的良好背景。我真的不知道专门为线程编写的任何类似内容。值得庆幸的是,尽管我们描述的这种情况(一个标志指示完成,如果设置了该标志,则其他数据被认为是有效的),这种情况并没有像硬件外围设备那样频繁地引起这些问题。问题...

使用volatile关键字向编译器提示该值可以随时更改。

volatile int myInteger;

上面的代码将确保对变量的任何访问都将在内存中进行,而无需进行任何特定的优化,因此,在同一处理器上运行的所有线程都将"看到"具有与代码读取相同语义的变量更改。

克里斯·杰斯特·扬(Chris Jester-Young)指出,在多处理器系统中可能会出现对此类变量值变化的一致性问题。这是一个考虑因素,它取决于平台。

实际上,相对于平台,确实要考虑两个注意事项。它们是内存事务的一致性和原子性。

原子性实际上是单处理器和多处理器平台的考虑因素。出现问题是因为该变量本质上可能是多字节的,而问题是一个线程是否可以看到该值的部分更新。即:某些字节已更改,上下文切换,中断线程读取的无效值。对于自然机器字大小或者更小的且自然对齐的单个变量,则不必担心。特别是,在这种情况下,只要对齐int类型,它就应该总是可以的,这应该是编译器的默认情况。

相对于一致性,这是多处理器系统中的潜在问题。问题是系统是否在处理器之间实现了完整的缓存一致性。如果实施,通常使用硬件中的MESI协议来完成。该问题并未说明平台,但是Intel x86平台和PowerPC平台在处理器之间的缓存均保持一致,以实现正常映射的程序数据区域。因此,即使存在多个处理器,对于线程之间的普通数据存储器访问也不必考虑这种类型的问题。

与原子性相关的最终问题是特定于读取-修改-写入原子性的。也就是说,如何保证如果读取一个值会更新其值并写入该值,那么这会原子发生,即使在多个处理器之间(即使在多个处理器上)也是如此。因此,要使其在没有特定同步对象的情况下工作,将要求所有访问该变量的潜在线程仅是读取器,但期望一次只能有一个线程成为写入器。如果不是这种情况,那么我们确实需要一个可用的同步对象,以确保对该变量的read-modify-write操作具有原子操作。