读取互锁变量

Mat*_*ttJ 25 c++ windows multithreading locking interlocked

假设:

A. WIN32下的C++.

B.正确对齐的易失性整数使用InterlockedIncrement()和递增和递减InterlockedDecrement().

__declspec (align(8)) volatile LONG _ServerState = 0;
Run Code Online (Sandbox Code Playgroud)

如果我想简单地读取_ServerState,我是否需要通过InterlockedXXX函数读取变量?

例如,我见过如下代码:

LONG x = InterlockedExchange(&_ServerState, _ServerState);
Run Code Online (Sandbox Code Playgroud)

LONG x = InterlockedCompareExchange(&_ServerState, _ServerState, _ServerState);
Run Code Online (Sandbox Code Playgroud)

目标是简单地读取当前的值_ServerState.

我不能简单地说:

if (_ServerState == some value)
{
// blah blah blah
}
Run Code Online (Sandbox Code Playgroud)

这个问题似乎有些混乱.我理解在Windows中注册大小的读取是原子的,所以我认为这个InterlockedXXX函数是不必要的.

马特J.


好的,谢谢你的回复.顺便说一句,这是Visual C++ 2005和2008.

如果这是真的,我应该使用一个InterlockedXXX函数来读取它的值_ServerState,即使只是为了清楚起见,最好的方法是什么?

LONG x = InterlockedExchange(&_ServerState, _ServerState);
Run Code Online (Sandbox Code Playgroud)

这具有修改值的副作用,当我真正想做的就是阅读它.不仅如此,如果存在上下文切换,我可以将标志重置为错误的值,因为_ServerState在准备调用时将值推到堆栈上InterlockedExchange().

LONG x = InterlockedCompareExchange(&_ServerState, _ServerState, _ServerState);
Run Code Online (Sandbox Code Playgroud)

我从我在MSDN上看到的一个例子中得到了这个.
http://msdn.microsoft.com/en-us/library/ms686355(VS.85).aspx

我所需要的只是一条线:

lock mov eax, [_ServerState]
Run Code Online (Sandbox Code Playgroud)

在任何情况下,我认为很清楚的一点是提供对标志的线程安全访问,而不会产生关键部分的开销.我已经看到LONG通过InterlockedXXX()函数族使用这种方式,因此我的问题.

好的,我们正在考虑解决当前值的这个问题的一个很好的解决方案是:

LONG Cur = InterlockedCompareExchange(&_ServerState, 0, 0);
Run Code Online (Sandbox Code Playgroud)

Mic*_*urr 13

这取决于你的意思是"目标是简单地读取_ServerState的当前值",它取决于你使用的工具集和平台(指定Win32和C++,但不指定哪个C++编译器,这可能很重要) .

如果你只是想读取值,使得该值没有损坏(即,如果某个其他处理器正在将值从0x12345678更改为0x87654321,则读取将获得这两个值中的一个而不是0x12344321)然后只需读取就可以了只要变量是:

  • 标记volatile,
  • 正确对齐,和
  • 使用单个指令读取,该指令具有处理器原子处理的字大小

这些都不是C/C++标准所承诺的,但Windows和MSVC确实做出了这些保证,我认为大多数针对Win32的编译器都可以.

但是,如果您希望将读取与其他线程的行为同步,则会有一些额外的复杂性.假设您有一个简单的"邮箱"协议:

struct mailbox_struct {
    uint32_t flag;
    uint32_t data;
};
typedef struct mailbox_struct volatile mailbox;


// the global - initialized before wither thread starts

mailbox mbox = { 0, 0 };

//***************************
// Thread A

while (mbox.flag == 0) { 
    /* spin... */ 
}

uint32_t data = mbox.data;

//***************************

//***************************
// Thread B

mbox.data = some_very_important_value;
mbox.flag = 1;

//***************************
Run Code Online (Sandbox Code Playgroud)

思路是线程A将等待mbox.flag旋转以指示mbox.data具有有效的信息.线程B将一些数据写入mailbox.data,然后将mbox.flag设置为1作为mbox.data有效的信号.

在这种情况下,即使后续读取线程A中的mbox.data没有获得线程B写入的值,mbox.flag的线程A中的简单读取也可能获得值1.

这是因为即使编译器不会重新排序线程B写入mbox.data和mbox.flag,处理器和/或缓存也可能.C/C++保证编译器将生成代码,使得线程B在写入mbox.flag之前将写入mbox.data,但处理器和缓存可能有不同的想法 - 称为"内存屏障"或"获取和"的特殊处理必须使用release semantics来确保低于线程指令流级别的排序.

我不确定MSVC以外的编译器是否对低于指令级别的订购提出任何要求.但是,MS确实保证MSVC volatile足够了 - MS指定volatile写入具有释放语义,而volatile读取具有获取语义 - 尽管我不确定这适用于哪个版本的MSVC - 请参阅http://msdn.microsoft.com /en-us/library/12a04hfd.aspx?ppud=4.

我还看到过你所描述的代码,它使用Interlocked API对共享位置执行简单的读写操作.我对此事的看法是使用Interlocked API.锁定自由的线程间通信充满了非常难以理解和微妙的陷阱,并试图抓住一个关键代码的快捷方式可能最终导致一个非常难以诊断的错误对我来说似乎不是一个好主意.此外,使用Interlocked API向维护代码的任何人尖叫,"这是需要与其他东西共享或同步的数据访问 - 谨慎行事! ".

此外,当使用Interlocked API时,您将从图片中获取硬件和编译器的细节 - 平台确保所有这些内容都得到妥善处理 - 不再有疑问......

阅读Herb Sutter关于DDJ 的有效并发文章(至少对于我来说恰好是失败的),以获取有关此主题的详细信息.

  • 互锁的API也会尖叫"小心翼翼".:) (2认同)
  • 这在今天可能是错误的.旧的答案,但由于它是最被接受的,如果你能纠正/评论目前的状态,我将不胜感激.首先,`volatile`确实有不同的含义,它可能在这里什么都不做.此外,在其他硬件架构上,图片可能会有所不同.然后,没有提到记忆障碍和"可见性".单独的原子性不足以解决所有并发问题 - 它们仍然是数据竞争.(另请参阅[Microsoft Specific/volatile:ms编译器选项](http://msdn.microsoft.com/en-us/library/12a04hfd.aspx) (2认同)

Ser*_*gey 6

您的方法很好:

LONG Cur = InterlockedCompareExchange(&_ServerState, 0, 0);
Run Code Online (Sandbox Code Playgroud)

我正在使用类似的解决方案:

LONG Cur = InterlockedExchangeAdd(&_ServerState, 0);
Run Code Online (Sandbox Code Playgroud)


Bar*_*ski 5

互锁指令提供原子性处理器间同步。写入和读取都必须同步,因此,是的,您应该使用互锁指令来读取线程之间共享且不受锁保护的值。无锁编程(这就是您要做的)是一个非常棘手的领域,因此您可以考虑使用锁。除非这确实是您程序的瓶颈之一,必须对其进行优化?

  • x86具有mfence指令的事实很好地表明了它具有宽松的内存模型。在严格(顺序一致)的模型中,不需要围栏。确切地说,x86实现了TSO模型(总存储订单)。我在博客中对其进行了描述:http://blog.corensic.com/2011/08/15/data-races-at-the-processor-level/。 (2认同)
  • 也许我们彼此误会了。从架构上讲,x86确实在其缓存中实现了MESI一致性协议。但是,写入操作不会直接进入高速缓存,因此会产生宽松的内存模型。 (2认同)

Cha*_*tin 0

应该没事的。它是易失性的,所以优化器不应该对你造成伤害,而且它是一个 32 位值,所以它至少应该是原子的。一个可能的惊喜是指令管道是否可以解决这个问题。

另一方面,使用受保护例程的额外成本是多少?