考虑多个线程并发执行以下代码:
long gf = 0;// global variable or class member
//...
if (InterlockedCompareExchange(&gf, 1, 0)==0) // lock cmpxchg
{
// some exclusive code - must not execute in concurrent
gf = 0; // this is ok ? or need
//InterlockedExchange(&gf, 0); // [lock] xchg
}
将上面的代码视为类似 C 的伪代码,这些代码将或多或少地直接转换为汇编,而无需对编译器优化(例如重新排序和存储消除)做出通常的让步。
因此,在某些线程独占获取标志 gf
之后 - 退出临界区是否足以写入一个零(如 gf = 0
中)或者这是否需要互锁 - InterlockedExchange(&gf, 0)
?
如果两者都正常,假设多个内核同时调用 InterlockedCompareExchange(&gf, 1, 0)
的可能性很高,从性能角度来看哪个更好?
多个线程周期性地执行此代码(从几个地方,当某些事件触发时),重要的是下一个线程在释放后尽快再次进入临界区。
最佳答案
相关:Spinlock with XCHG解释为什么你不需要需要xchg
释放 x86 asm 中的锁,只需一条存储指令。
但是在 C++ 中,您需要比普通 gf = 0;
更强大的东西在平原上 long gf
变量。C/C++ 内存模型(对于普通变量)是非常弱排序的,即使在针对强排序的 x86 进行编译时也是如此,因为这对于优化至关重要。
您需要一个释放存储来正确释放锁,不允许临界区中的操作通过在编译时或运行时使用 gf=0
重新排序而泄漏出临界区。店铺。 http://preshing.com/20120913/acquire-and-release-semantics/ .
由于您使用的是 long gf
, 不是 volatile long gf
,并且您没有使用编译器内存屏障,您的代码中没有任何内容可以阻止编译时重新排序。 (x86 asm 存储具有发布语义,因此我们需要担心的只是编译时重新排序。)http://preshing.com/20120625/memory-ordering-at-compile-time/
我们使用 std::atomic<long> gf;
尽可能便宜地获得我们需要的一切和 gf.store(0, std::memory_order_release);
atomic<long>
在支持 InterlockedExchange
的每个平台上都是无锁的, AFAIK,所以你应该可以混合搭配。 (或者只使用 gf.exchange()
获取锁。如果滚动你自己的锁,请记住你应该在只读操作上循环 + _mm_pause()
在等待锁时,不要用 xchg
敲打或 lock cmpxchg
并可能延迟解锁。请参阅 Locks around memory manipulation via inline assembly。
这是 Why is integer assignment on a naturally aligned variable atomic on x86? 中警告的情况之一。你需要的 atomic<>
确保编译器实际在您需要的地方/时间进行存储。
关于multithreading - 退出临界区,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50221295/