退出临界区
Exit critical region
考虑多个线程同时执行以下代码:
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)
?
如果两个都OK,从性能的角度来看哪个更好,假设几个核心同时调用的可能性很大 InterlockedCompareExchange(&gf, 1, 0)
?
多个线程周期性地执行此代码(从几个地方,当某些事件触发时),重要的是下一个线程在释放后尽快再次进入临界区。
gf = 0
就足够了。无需使用锁定操作,因为没有其他线程可以更改其值。
顺便说一下,我会使用 bts 而不是 cmpxchg 来获取锁。我不确定它是否对性能有任何影响,但它更简单。
相关:Spinlock with XCHG 解释了为什么你 不需要 需要 xchg
释放 x86 asm 中的锁,只是一条存储指令。
但是 在 C++ 中,你需要比普通 long gf
变量上的普通 gf = 0;
更强大的东西。 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
,据我所知,所以你应该可以混合搭配。 (或者只使用 gf.exchange()
获取锁。如果滚动你自己的锁,请记住你应该循环只读操作 + _mm_pause()
在等待锁时,不要敲打使用 xchg
或 lock cmpxchg
并可能延迟解锁。参见 。
这是 中的警告之一,您需要 atomic<>
以确保编译器实际在您需要的位置/时间应用存储。
考虑多个线程同时执行以下代码:
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)
?
如果两个都OK,从性能的角度来看哪个更好,假设几个核心同时调用的可能性很大 InterlockedCompareExchange(&gf, 1, 0)
?
多个线程周期性地执行此代码(从几个地方,当某些事件触发时),重要的是下一个线程在释放后尽快再次进入临界区。
gf = 0
就足够了。无需使用锁定操作,因为没有其他线程可以更改其值。
顺便说一下,我会使用 bts 而不是 cmpxchg 来获取锁。我不确定它是否对性能有任何影响,但它更简单。
相关:Spinlock with XCHG 解释了为什么你 不需要 需要 xchg
释放 x86 asm 中的锁,只是一条存储指令。
但是 在 C++ 中,你需要比普通 long gf
变量上的普通 gf = 0;
更强大的东西。 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
,据我所知,所以你应该可以混合搭配。 (或者只使用 gf.exchange()
获取锁。如果滚动你自己的锁,请记住你应该循环只读操作 + _mm_pause()
在等待锁时,不要敲打使用 xchg
或 lock cmpxchg
并可能延迟解锁。参见
这是 atomic<>
以确保编译器实际在您需要的位置/时间应用存储。