如何确保变量在不同线程读取之前存储到内存中

时间:2012-05-22 16:59:12

标签: c++ multithreading visual-c++

更新:我以另一种形式(见下文)提出这个问题,因为没有建设性而被关闭了。有点遗憾,因为答案完全处理了我的问题(并解决了我的问题),但我是新来的,所以我一定会再试一次,使它更有建设性。

我在Windows 7下使用VC ++。我的多线程程序将值分配给一个线程中的变量,然后通过事件对象将信号发送到被阻塞的另一个线程,等待该信号。由于编译器提供的优化之类的东西,无法保证一个线程分配给变量的数据实际上可用于另一个线程,即使有人确定(通过阻塞机制)另一个线程不会尝试访问,直到数据分配给变量后的时间。例如,该值可能位于CPU寄存器中,保留在那里直到其他内容需要该寄存器。如果在将该值放入该寄存器后很快再次需要,这可以避免来自内存的不必要的负载。不幸的是,这意味着内存中的相应位置继续保持它在分配新值之前所持有的最后一个值。因此,当另一个线程解除阻塞并访问保存变量值的内存时,它将获得值,而不是最近分配的值。

然后,问题是:一个Windows线程如何将内存强制存储到它分配给变量的值,以便另一个线程确定以后可以访问它们?可能有几个答案,但在这个问题被解决之前提供的答案似乎最适合我需要的是使用“记忆围栏”,这是我以前没有听说过的编程结构。遇到栅栏后,保证已完成对存储器的挂起写入。 (如果围栏是一个“写”围栏;可以强制从内存中读取“读取”围栏,并且可以使用“读/写”围栏进行读取.Windows使得VC ++中的所有三个都很容易可用程序。)

一个轻微的问题是,Windows围栏(又名“内存障碍”)仅将其保证应用于全局存储(而非本地存储)(原因在适用的MSDN页面上说明)。

如果我在此解释记忆围栏的工作方式是不正确的(并且主持人重新打开了这个问题),我很高兴看到评论中解释的内容。毕竟,我不会问我是不是很谦虚地承认我不知道。 (如果主持人没有重新打开它,但你可以看到我有问题,请给我发电子邮件告诉我;我很乐意帮助在我的博客上继续讨论这个问题,如果你这样做。)

原始版本
在线程之间共享数据的好方法是什么?

我之前问a question volatile变量为我开辟了一个巨大的学习经历。除此之外,我意识到我没有问正确的问题。希望这不是糟糕的stackoverflow礼仪,但我想我应该在这里创建一个新的问题来解决我的根本问题:

我的Visual C ++程序中有两个线程A和B. B被阻塞,等待来自A的信号.A设置了许多变量。 A然后发出信号B,它将读取由A设置的变量。我担心A设置的某些变量实际上可能不会被写回内存,因为它们可能只存在于CPU寄存器中。

有什么好的方法可以确保线程B在读取先前由线程A设置的变量时,读取线程A设置的值?

4 个答案:

答案 0 :(得分:3)

在x86架构下,使用好的库时没有太多担心。

使用互斥锁(例如boost :: mutex)保护对共享数据的访问,如果互斥锁的实现者做得对,那么他/她将使用内存屏障(Memory Barriers @ MSDN)来确保缓存已被冲到内存中。

如果您必须编写自己的同步代码,请为其添加内存屏障。

答案 1 :(得分:1)

您在评论中提到,我的特殊问题在于能够保证,一旦解锁,线程实际上可以访问由其他线程写入共享位置的值

我相信你的问题的答案很简单:你可以使用_ReadWriteBarrier()(或者,在你的特定情况下,可能只是_WriteBarrier在阅读线程内),以确保你阅读 - 最新的记忆值。

请注意,据我所知,在C / C ++中,volatile 保证有任何内存屏障语义 - 所以你不能简单地使用{{1在那些语言中。记忆障碍是简单阅读最新值的方法。

答案 2 :(得分:0)

这就像问“编写面向对象程序的好方法”。除了这个问题,我会说“去读一本好书”,但对于这个问题,确实没有关于坏范式的好书。许多多线程编程基于最小化共享数据的使用而不是使用它。

所以,我的建议是:

1)设计使得没有两个线程需要以这种方式相互通信。听起来更像是一个程序性线程,而不是两个真正独立的线程。

2)在流程内或流程之间实现面向服务的体系结构。使所有共享数据在短暂的请求/响应模式上发生,而不是依赖于轮询的全局变量的使用。 A设置并告诉B读取的所有这些变量听起来很像“客户”A发送到“服务器”B的“请求”。

3)如果你可以安装并努力学习图书馆,我推荐ZMQ。我有很好的使用经验,并且他们宣传(并根据我的经验)他们的工具,在服务上看起来像一个库来实现客户端和服务器,作为一种方法来摆脱线程之间的所有共享数据。如果没有其他任何内容,那么文档可能会为您提供很好的方法来考虑在线程之间兑换您的共享数据以获取不涉及它们的模式。

答案 3 :(得分:0)

  

如果您的意思是“我如何同步访问以防止竞争条件?”我想我已经通过在等待来自另一个的信号时让每个线程阻塞来管理。我的特殊问题是能够保证,一旦解除阻塞,线程实际上可以访问由另一个线程写入共享位置的值。

是的,确切地说。问题是等待某个线程设置的信号不足以确保该线程的任何其他活动在当前线程中是可见的。线程可以设置变量,触发信号,然后等待信号的线程可以访问变量,但获得完全不同的值。

我目前正在享受关于此主题的Anthony Williams'本书 C++ Concurrency in Action 。答案似乎在于正确使用std :: atomic内存命令。这是一个例子:

std::atomic<bool> signal(false);
std::atomic<int> i(0);

-- thread 1 --
i.store(100,std::memory_order_relaxed);
signal.store(true,std::memory_order_release);

-- thread 2 --
while(!signal.load(std::memory_order_acquire));
assert(i.load(std::memory_order_relaxed) == 100);

当第二个线程看到信号时,在使用memory_order_release执行的存储与使用memory_order_acquire执行的加载之间建立关系,这保证了对i的存储将在第二个线程中可见。因此,保证断言。

另一方面,如果您使用不太严格的记忆订单,那么您将得不到任何保证。

-- thread 1 --
i.store(100,std::memory_order_relaxed);
signal.store(true,std::memory_order_relaxed);

-- thread 2 --
while(!signal.load(std::memory_order_relaxed));
int i2 = i.load(memory_order_relaxed);
// No guarantees about the value loaded from i!

或者,您可以使用默认的内存顺序,只要您没有任何数据争用,就可以保证顺序一致性。

std::atomic<bool> signal(false);
int i = 0;

-- thread 1 --
i = 100;
signal = true;

-- thread 2 --
while(!signal);
assert(i == 100);