C++线程,共享数据

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/118199/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:58:57  来源:igfitidea点击:

C++ Thread, shared data

c++multithreadingsynchronizationmutex

提问by fabiopedrosa

I have an application where 2 threads are running... Is there any certanty that when I change a global variable from one thread, the other will notice this change? I don't have any syncronization or Mutual exclusion system in place... but should this code work all the time (imagine a global boolnamed dataUpdated):

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

Thread 1:

主题 1:

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

Thread 2:

主题 2:

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

Does a compiler like gcc optimize this code in a way that it doesn't check for the global value, only considering it value at compile time (because it nevers get changed at the same thred)?

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

PS: Being this for a game-like application, it really doen't matter if there will be a read while the value is being written... all that matters is that the change gets noticed by the other thread.

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

采纳答案by puetzk

Yes. No. Maybe.

是的。不,也许吧。

First, as others have mentioned you need to make dataUpdated volatile; otherwise the compiler may be free to lift reading it out of the loop (depending on whether or not it can see that doSomethingElse doesn't touch it).

首先,正如其他人提到的,您需要使 dataUpdated 变得易变;否则编译器可以自由地从循环中读取它(取决于它是否可以看到 doSomethingElse 没有触及它)。

Secondly, depending on your processor and ordering needs, you may need memory barriers. volatile is enough to guarentee that the other processor will see the change eventually, but not enough to guarentee that the changes will be seen in the order they were performed. Your example only has one flag, so it doesn't really show this phenomena. If you need and use memory barriers, you should no longer need volatile

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

Volatile considered harmfuland Linux Kernel Memory Barriersare good background on the underlying issues; I don't really know of anything similar written specifically for threading. Thankfully threads don't raise these concerns nearly as often as hardware peripherals do, though the sort of case you describe (a flag indicating completion, with other data presumed to be valid if the flag is set) is exactly the sort of thing where ordering matterns...

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

回答by 1800 INFORMATION

Here is an example that uses boost condition variables:

下面是一个使用 boost 条件变量的例子:

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

回答by Niall

Use a lock. Always always use a lock to access shared data. Marking the variable as volatile will prevent the compiler from optimizing away the memory read, but will not prevent other problems such as memory re-ordering. Without a lock there is no guarantee that the memory writes in doSomething() will be visible in the updateScreen() function.

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

The only other safe way is to use a memory fence, either explicitly or an implicitly using an Interlocked* function for example.

唯一的其他安全方法是使用内存栅栏,例如显式或隐式使用 Interlocked* 函数。

回答by Tall Jeff

Use the volatilekeyword to hint to the compiler that the value can change at any time.

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

volatile int myInteger;

The above will guarantee that any access to the variable will be to and from memory without any specific optimizations and as a result all threads running on the same processor will "see" changes to the variable with the same semantics as the code reads.

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

Chris Jester-Young pointed out that coherency concerns to such a variable value change may arise in a multi-processor systems. This is a consideration and it depends on the platform.

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

Actually, there are really two considerations to think about relative to platform. They are coherency and atomicity of the memory transactions.

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

Atomicity is actually a consideration for both single and multi-processor platforms. The issue arises because the variable is likely multi-byte in nature and the question is if one thread could see a partial update to the value or not. ie: Some bytes changed, context switch, invalid value read by interrupting thread. For a single variable that is at the natural machine word size or smaller and naturally aligned should not be a concern. Specifically, an inttype should always be OK in this regard as long as it is aligned - which should be the default case for the compiler.

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

Relative to coherency, this is a potential concern in a multi-processor system. The question is if the system implements full cache coherency or not between processors. If implemented, this is typically done with the MESI protocol in hardware. The question didn't state platforms, but both Intel x86 platforms and PowerPC platforms are cache coherent across processors for normally mapped program data regions. Therefore this type of issue should not be a concern for ordinary data memory accesses between threads even if there are multiple processors.

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

The final issue relative to atomicity that arises is specific to read-modify-write atomicity. That is, how do you guarantee that if a value is read updated in value and the written, that this happen atomically, even across processors if more than one. So, for this to work without specific synchronization objects, would require that all potential threads accessing the variable are readers ONLY but expect for only one thread can ever be a writer at one time. If this is not the case, then you do need a sync object available to be able to ensure atomic actions on read-modify-write actions to the variable.

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

回答by Tall Jeff

Your solution will use 100% CPU, among other problems. Google for "condition variable".

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

回答by fabiopedrosa

Chris Jester-Young pointed out that:

Chris Jester-Young 指出:

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

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

being so, the only true answer is implementing a synchronization system, right?

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

回答by Adam Pierce

Use the volatilekeyword to hint to the compiler that the value can change at any time.

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

volatile int myInteger;

回答by Lou Franco

No, it's not certain. If you declare the variable volatile, then the complier is supposed to generate code that always loads the variable from memory on a read.

不,这并不确定。如果将变量声明为 volatile,那么编译器应该生成的代码总是在读取时从内存中加载变量。

回答by Andrew Edgecombe

As others have said the volatilekeyword is your friend. :-)

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

You'll most likely find that your code would work when you had all of the optimisation options disabled in gcc. In this case (I believe) it treats everything as volatile and as a result the variable is accessed in memory for every operation.

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

With any sort of optimisation turned on the compiler will attempt to use a local copy held in a register. Depending on your functions this may mean that you only see the change in variable intermittently or, at worst, never.

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

Using the keyword volatileindicates to the compiler that the contents of this variable can change at any time and that it should notuse a locally cached copy.

使用关键字volatile指示编译器这个变量的内容可以随时更改,它应该使用本地缓存副本。

With all of that said you may find better results (as alluded to by Jeff) through the use of a semaphore or condition variable.

综上所述,通过使用信号量或条件变量,您可能会发现更好的结果(正如Jeff所暗示的那样)。

Thisis a reasonable introduction to the subject.

是对该主题的合理介绍。

回答by kervin

If the scope is right ( "extern", global, etc. ) then the change will be noticed. The question is when? And in what order?

如果范围是正确的(“extern”、global 等),那么就会注意到变化。问题是什么时候?以及按什么顺序?

The problem is that the compiler canand frequently willre-order your logic to fill all it's concurrent pipelines as a performance optimization.

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

It doesn't really show in your specific example because there aren't any other instructions around your assignment, but imagine functions declared after your bool assign execute beforethe assignment.

它并没有真正显示在您的具体示例中,因为您的赋值没有任何其他指令,但是想象一下在您的 bool assign 之后声明的函数在赋值之前执行。

Check-out Pipeline Hazardon wikipedia or search google for "compiler instruction reordering"

在维基百科上检查管道危害或在谷歌搜索“编译器指令重新排序”