4

更新:我以另一种形式提出了这个问题(见下文),但由于不具建设性而被关闭。有点遗憾,因为答案完全符合我的要求(并解决了我的问题),但我是新来的,所以我一定会再次尝试使其更具建设性。

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

那么问题是:一个 Windows 线程如何强制将其分配给变量的值存储到内存中,以便另一个线程在以后确定可以访问它们?可能有几个答案,但在这个问题结束之前提供的一个似乎最适合我需要的答案是使用“内存栅栏”,这是我以前从未听说过的编程结构。遇到栅栏后,保证已完成对内存的挂起写入。(如果栅栏是“写”栅栏;可以使用“读取”栅栏强制从内存中读取,并且可以使用“读/写”栅栏同时执行这两种操作。Windows 在 VC++ 中很容易实现这三个程序。)

一个小问题原来是 Windows 栅栏(又名“内存屏障”)仅将其保证应用于全局而非本地存储(原因在适用的 MSDN 页面上进行了解释)。

如果我在这里对记忆栅栏如何工作的解释不正确(并且版主曾经重新打开这个问题),我很高兴在评论中看到这一点。毕竟,我不会问我是否谦虚到承认我不知道。(如果版主没有重新打开它,但你可以看到我有问题,请给我发电子邮件并让我知道;我很乐意帮助在我的博客上保持这个讨论的活力,如果你这样做。)

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

我之前问了一个关于volatile变量的问题,这些变量为我带来了巨大的学习经验。除其他外,我意识到我没有问对正确的问题。希望这不是坏的stackoverflow礼仪,但我认为我应该在这里创建一个新问题来解决我的潜在问题:

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

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

4

4 回答 4

3

在 x86 架构下,使用好的库无需担心太多。

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

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

于 2012-05-22T17:20:31.600 回答
1

您在评论中提到,我的特殊问题是能够保证,一旦解除阻塞,一个线程实际上将有权访问另一个线程写入共享位置的值

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

请注意,据我所知,在 C/C++ 中,volatile不能保证具有任何内存屏障语义——因此您不能简单地volatile在这些语言中使用。内存屏障是简单地读取最新值的方法。

于 2012-05-22T17:19:38.697 回答
0

这就像在问“编写面向对象程序的好方法是什么”。除了那个问题,我会说“去读一本好书”,但对于这个问题,真的没有一本关于坏范式的好书。许多多线程编程都是基于尽量减少共享数据的使用,而不是很好地使用它。

所以,我的建议是:

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

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

3)如果您可以安装并努力学习库,我推荐 ZMQ。我在这方面有很好的经验,他们宣传(并根据我的经验提供)他们的工具,该工具在服务上看起来像一个用于实现客户端和服务器的库,作为摆脱线程之间所有共享数据的一种方式。如果不出意外,文档可能会为您提供很好的方法来考虑在线程之间兑现您的共享数据以获取不涉及它们的模式。

于 2012-05-22T17:13:13.583 回答
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);
于 2012-05-22T18:15:34.550 回答