C ++线程,共享数据

时间:2008-09-22 23:22:07

标签: c++ multithreading synchronization mutex

我有一个运行2个线程的应用程序......当我从一个线程更改全局变量时,另一个会注意到这个变化吗? 我没有任何同步或互斥系统...但是这段代码应该一直有效(想象一下全局 bool 命名为 dataUpdated ):

主题1:

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

主题2:

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

像gcc这样的编译器是否会以不检查全局值的方式优化此代码,只在编译时考虑它的值(因为它会在同一个thred中更改)?

PS:对于类似游戏的应用程序而言,在写入值时是否会有读取真的无关紧要......重要的是其他线程会注意到更改。 / p>

10 个答案:

答案 0 :(得分:24)

是。不,也许。

首先,正如其他人提到的那样,你需要使dataUpdated易变;否则编译器可以自由地将其读出循环(取决于它是否可以看到doSomethingElse不接触它)。

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

Volatile considered harmfulLinux Kernel Memory Barriers是潜在问题的良好背景;我真的不知道有什么类似的专门针对线程编写的。值得庆幸的是,线程并不像硬件外设那样经常引起这些问题,尽管你描述的情况(指示完成的标志,如果设置了标志,其他数据被认为是有效的)正是排序的事情。 matterns ...

答案 1 :(得分:7)

以下是使用增强条件变量的示例:

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;
  }
}

答案 2 :(得分:7)

使用锁。始终使用锁来访问共享数据。将变量标记为volatile将阻止编译器优化内存读取,但不会阻止其他问题,例如memory re-ordering。如果没有锁定,则无法保证doSomething()中的内存写入将在updateScreen()函数中可见。

唯一安全的方法是使用memory fence,例如显式或隐式使用Interlocked *函数。

答案 3 :(得分:6)

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

volatile int myInteger;

上述内容将保证对变量的任何访问都将在没有任何特定优化的情况下进出内存,因此在同一处理器上运行的所有线程将“看到”对变量的更改,其语义与代码读取相同

Chris Jester-Young指出,在多处理器系统中可能出现对这种可变值变化的一致性问题。这是一个考虑因素,它取决于平台。

实际上,相对于平台来说,有两个需要考虑的因素。它们是记忆交易的一致性和原子性。

原子性实际上是单处理器和多处理器平台的考虑因素。出现这个问题是因为变量本质上可能是多字节的,问题是一个线程是否可以看到值的部分更新。即:一些字节改变,上下文切换,通过中断线程读取无效值。对于处于自然机器字大小或更小且自然对齐的单个变量不应该是一个问题。具体来说, int 类型在这方面应始终没问题,只要它是对齐的 - 这应该是编译器的默认情况。

相对于一致性,这是多处理器系统中的潜在问题。问题是系统是否在处理器之间实现完全缓存一致性。如果实现,通常使用硬件中的MESI协议来完成。这个问题没有说明平台,但是英特尔x86平台和PowerPC平台在处理器之间对于正常映射的程序数据区域是高速缓存一致的。因此,即使存在多个处理器,这种类型的问题也不应成为线程之间普通数据存储器访问的问题。

与原子性相关的最后一个问题是读 - 修改 - 写原子性特有的。也就是说,如果一个值被读取更新的值和写入,你如何保证这是原子地发生,甚至跨处理器如果不止一个。因此,为了在没有特定同步对象的情况下工作,将要求访问变量的所有潜在线程仅是读者,但期望一次只有一个线程可以成为写入者。如果不是这种情况,那么您确实需要一个可用的同步对象,以确保对变量进行读 - 修改 - 写操作的原子操作。

答案 4 :(得分:3)

您的解决方案将使用100%CPU,以及其他问题。谷歌的“条件变量”。

答案 5 :(得分:3)

Chris Jester-Young指出:

  

这只适用于Java 1.5 +的内存模型。 C ++标准没有解决线程问题,而volatile也不能保证处理器之间的内存一致性。你确实需要一个内存屏障

如此,唯一真正的答案是实现同步系统,对吗?

答案 6 :(得分:2)

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

volatile int myInteger;

答案 7 :(得分:2)

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

答案 8 :(得分:1)

如果范围是正确的(“extern”,全球等),则会注意到更改。问题是什么时候?以什么顺序?

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

它并没有真正显示在你的具体例子中,因为你的作业没有任何其他说明,但想象一下你的bool分配后在分配之前执行时声明的函数。

在维基百科上查看Pipeline Hazard或在google上搜索“编译器指令重新排序”

答案 9 :(得分:1)

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

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

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

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

所有这些都说明你可以通过使用信号量或条件变量找到更好的结果(正如Jeff所提到的那样)。

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