读取互锁variables

承担:

A. WIN32下的C ++。

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

 __declspec (align(8)) volatile LONG _ServerState = 0; 

如果我只想读取_ServerState,是否需要通过InterlockedXXX函数读取variables?

例如,我看过如下的代码:

 LONG x = InterlockedExchange(&_ServerState, _ServerState); 

 LONG x = InterlockedCompareExchange(&_ServerState, _ServerState, _ServerState); 

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

我不能简单地说:

 if (_ServerState == some value) { // blah blah blah } 

似乎有一些WRT这个问题混淆。 我知道寄存器大小的读取在Windows中是primefaces的,所以我认为InterlockedXXX函数是不必要的。

马特·J


好的,谢谢你的回应。 顺便说一下,这是Visual C ++ 2005和2008。

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

 LONG x = InterlockedExchange(&_ServerState, _ServerState); 

当我真正想做的事情是读取它时,这有修改价值的副作用。 不仅如此,如果存在上下文切换,则可能会将该标志重置为错误值,因为在调用InterlockedExchange()准备过程_ServerState的值压入堆栈。

 LONG x = InterlockedCompareExchange(&_ServerState, _ServerState, _ServerState); 

我从MSDN上看到的一个例子中拿出了这个。
请参阅http://msdn.microsoft.com/en-us/library/ms686355(VS.85).aspx

我需要的只是一些事情而已:

 lock mov eax, [_ServerState] 

在任何情况下,我认为清楚的一点是提供对一个标志的线程安全访问,而不会招致关键部分的开销。 我已经看到LONGs通过InterlockedXXX()系列函数使用这种方式,所以我的问题。

好吧,我们正在考虑一个很好的解决方法来解决这个读取当前值的问题是:

 LONG Cur = InterlockedCompareExchange(&_ServerState, 0, 0); 

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

如果你只是想读这个值,使得这个值没有被破坏(也就是说,如果其他处理器正在将值从0x12345678更改为0x87654321,那么你的读取将得到这两个值中的一个,而不是0x12344321),那么简单地读取将是OK只要变量是:

  • 显着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; //*************************** 

思想是线程A将自旋等待mbox.flag来指示mbox.data有一个有效的信息。 线程B会将一些数据写入mailbox.data然后将mbox.flag设置为1,作为mbox.data有效的信号。

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

这是因为即使编译器不会将线程B写入到mbox.data和mbox.flag中,处理器和/或缓存也可能会这样。 C / C ++保证编译器将生成代码,以便线程B在写入mbox.flag之前写入mbox.data,但处理器和缓存可能有不同的想法 – 特殊处理称为“内存障碍”或“获取必须使用“释放语义”来确保在线程的指令流级别之下进行排序。

我不确定除了MSVC以外的编译器是否会在指令级别下进行排序。 不过,MS确保MSVC的挥发性就足够了 – MS指定volatile写入具有释放语义,volatile读取获取语义 – 尽管我不确定这适用于哪个版本的MSVC – 请参阅http://msdn.microsoft.com /en-us/library/12a04hfd.aspx?ppud=4 。

我也看到了像您描述的那样使用互锁API来执行简单读取和写入共享位置的代码。 我关心的是使用Interlocked API。 锁定免费的线程间通信是非常困难的理解和微妙的陷阱,并试图采取一个关键的代码,可能最终以一个非常困难的诊断错误的快捷方式似乎不是一个好主意,我。 此外,使用Interlocked API尖叫维护代码的任何人,“这是数据访问需要共享或与别的同步 – 谨慎行事! ”。

另外,当使用Interlocked API时,您将硬件和编译器的细节排除在图片之外 – 平台确保所有这些东西都得到了正确的处理 – 没有更多的想法…

阅读关于DDJ的Herb Sutter的有效并发性文章 (对于我来说至少在这个时候正好处于下降状态),以获得有关此主题的良好信息。

互锁指令提供原子性处理器间同步。 写和读都必须是同步的,所以是的,你应该使用互锁的指令来读取线程之间共享的值,而不受锁的保护。 无锁编程(这就是你正在做的)是一个非常棘手的领域,所以你可能会考虑使用锁。 除非这真的是你的程序的瓶颈之一,必须优化?

你的方式很好:

 LONG Cur = InterlockedCompareExchange(&_serverState, 0, 0); 

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

 LONG Cur = InterlockedExchangeAdd(&_serverState, 0); 

某些 32位系统上,32位读取操作已经是原子化了(英特尔规范说这些操作是原子操作,但不能保证在其他x86兼容平台上这种操作是正确的)。 所以你不应该使用这个线程同步。

如果你需要一个标志,你应该考虑使用Event对象和WaitForSingleObject函数。

应该没问题。 它是不稳定的,所以优化器不应该野蛮的,它是一个32位的值,所以它应该至少近似原子。 一个可能的惊喜是,如果指令管道可以解决这个问题。

另一方面,使用守护程序的额外成本是多少?

当前值读数可能不需要任何锁定。

Interlocked *函数可以防止两个不同的处理器访问同一块内存。 在一个单一的处理器系统,你会没事的。 如果你有一个双核系统,你在不同的内核上都有线程访问这个值,那么在没有Interlocked *的情况下,你可能会遇到你认为是原子的问题。

你最初的理解基本上是正确的。 根据Windows所支持的所有MP平台所支持(或曾经支持)的内存模型,只要它们小于机器字的大小,从标记为volatile的自然对齐变量读取就是原子的。 与写入相同。 您不需要“锁定”前缀。

如果您在不使用互锁的情况下进行读取操作,您将受到处理器重新排序的影响。 这甚至可以发生在x86上,在有限的情况下:从一个变量读取可能会被移到一个不同的变量写入。 在几乎所有Windows支持的非x86体系结构上,如果不使用显式的联锁,则会面临更复杂的重新排序。

还有一个要求是,如果你使用比较交换循环,你必须把你比较交换的变量标记为volatile。 这里有一个代码示例来说明原因:

 long g_var = 0; // not marked 'volatile' -- this is an error bool foo () { long oldValue; long newValue; long retValue; // (1) Capture the original global value oldValue = g_var; // (2) Compute a new value based on the old value newValue = SomeTransformation(oldValue); // (3) Store the new value if the global value is equal to old? retValue = InterlockedCompareExchange(&g_var, newValue, oldValue); if (retValue == oldValue) { return true; } return false; } 

可能会出错的是,如果编译器不是volatile的,那么编译器在任何时候都可以从g_var中重新获取oldValue。 这种“重新实现”的优化在很多情况下是非常好的,因为当寄存器压力很高的时候,它可以避免溢出寄存器到堆栈。

因此,该功能的步骤(3)将变成:

 // (3) Incorrectly store new value regardless of whether the global // is equal to old. retValue = InterlockedCompareExchange(&g_var, newValue, g_var); 

阅读很好。 只要不在高速缓存线上分割,32位值总是作为一个整体读取。 你对齐8保证它总是在一个缓存行,所以你会没事的。

忘记指令重新排序和所有无意义的。 结果总是按顺序退休。 这将是一个处理器,否则召回!

即使对于一个双CPU机器(即通过最慢的FSB共享),你仍然会没事,因为CPU保证通过MESI协议的高速缓存一致性。 唯一不能保证的是你阅读的价值可能不是最新的。 但是,什么最新的呢? 如果不根据读取的值写回到位置,那么在大多数情况下,您可能不需要知道这一点。 否则,你首先会使用联锁操作来处理它。

简而言之,通过在阅读中使用互锁操作,你什么也得不到(除了可能提醒下一个人维护你的代码以便小心行事 – 那么再次,这个人可能没有资格保持你的代码开始)。

编辑:在回应阿德里安·麦卡锡留下的评论。

您忽略了编译器优化的效果。 如果编译器认为它已经在寄存器中,那么它将重新使用该值,而不是从内存中重新读取它。 另外,如果编译器认为没有可观察的副作用,那么编译器可能会对指令进行重新排序以进行优化。

我没有说从一个非易失性变量读取是好的。 所有的问题是要求是否需要互锁。 事实上,有问题的变量被明确声明为volatile 。 或者你是否忽略了关键字volatile

对于任何需要重新访问这个线程的人,我想添加一下Bartosz的解释,如果标准的atomics不可用, _InterlockedCompareExchange()是标准的atomic_load()一个很好的选择。 以下是在i86 Win64上用C原子读取my_uint32_t_var的代码。 包含atomic_load()作为基准:

  long debug_x64_i = std::atomic_load((const std::_Atomic_long *)&my_uint32_t_var); 00000001401A6955 mov eax,dword ptr [rbp+30h] 00000001401A6958 xor edi,edi 00000001401A695A mov dword ptr [rbp-0Ch],eax debug_x64_i = _InterlockedCompareExchange((long*)&my_uint32_t_var, 0, 0); 00000001401A695D xor eax,eax 00000001401A695F lock cmpxchg dword ptr [rbp+30h],edi 00000001401A6964 mov dword ptr [rbp-0Ch],eax debug_x64_i = _InterlockedOr((long*)&my_uint32_t_var, 0); 00000001401A6967 prefetchw [rbp+30h] 00000001401A696B mov eax,dword ptr [rbp+30h] 00000001401A696E xchg ax,ax 00000001401A6970 mov ecx,eax 00000001401A6972 lock cmpxchg dword ptr [rbp+30h],ecx 00000001401A6977 jne foo+30h (01401A6970h) 00000001401A6979 mov dword ptr [rbp-0Ch],eax long release_x64_i = std::atomic_load((const std::_Atomic_long *)&my_uint32_t_var); 00000001401A6955 mov eax,dword ptr [rbp+30h] release_x64_i = _InterlockedCompareExchange((long*)&my_uint32_t_var, 0, 0); 00000001401A6958 mov dword ptr [rbp-0Ch],eax 00000001401A695B xor edi,edi 00000001401A695D mov eax,dword ptr [rbp-0Ch] 00000001401A6960 xor eax,eax 00000001401A6962 lock cmpxchg dword ptr [rbp+30h],edi 00000001401A6967 mov dword ptr [rbp-0Ch],eax release_x64_i = _InterlockedOr((long*)&my_uint32_t_var, 0); 00000001401A696A prefetchw [rbp+30h] 00000001401A696E mov eax,dword ptr [rbp+30h] 00000001401A6971 mov ecx,eax 00000001401A6973 lock cmpxchg dword ptr [rbp+30h],ecx 00000001401A6978 jne foo+31h (01401A6971h) 00000001401A697A mov dword ptr [rbp-0Ch],eax