【问题标题】:Exit critical region退出临界区
【发布时间】:2018-10-17 16:16:52
【问题描述】:

考虑多个线程同时执行以下代码:

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)的概率很高?

多个线程定期执行此代码(从多个位置,当某些事件触发时),重要的是下一个线程在释放后尽快再次进入临界区。

【问题讨论】:

  • 我会使用更详细的标题并在此处添加几个标签,您将获得更多帮助。
  • @Ryanman - 这个问题(我如何看待)仅与 x86-x64 内存顺序有关。与编译器、语言、操作系统无关。所以不要在这里查看可以使用哪些更多标签或更多详细信息
  • 正如彼得的回答所示,它肯定与 la 语言和编译器有关。作为没有 re-oredring 编译器的伪代码,它可以工作,作为具有特定内存模型的实际 C 或 C++,它不能工作。

标签: multithreading x86 locking x86-64 mutual-exclusion


【解决方案1】:

相关:Spinlock with XCHG 解释了为什么您不需要需要xchg 来释放 x86 asm 中的锁,只需一条存储指令。

但是在 C++ 中,你需要比普通的 gf = 0; 更强大的东西,而不是普通的 long gf 变量。 C / C++ 内存模型(用于普通变量)是非常弱排序的,即使在为强排序 x86 编译时也是如此,因为这对于优化至关重要。

您需要一个 release-store 才能正确释放锁,而不允许临界区中的操作通过在编译时或运行时使用 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() 在等待锁时,不要用xchglock cmpxchg 并可能延迟解锁。请参阅Locks around memory manipulation via inline assembly

这是Why is integer assignment on a naturally aligned variable atomic on x86? 中警告您需要atomic<> 以确保编译器实际执行存储的情况之一。

【讨论】:

    【解决方案2】:

    gf = 0 就足够了。没有必要使用锁定操作,因为没有其他线程可以更改它的值。

    顺便说一句,我会使用 bts 而不是 cmpxchg 来获取锁。我不确定它是否对性能有任何影响,但它更简单。

    【讨论】:

    • 实际上我使用InterlockedCompareExchangePointer 将一些指针值保存在指针(寄存器)大小变量中。立即将其用作关键区域获取和数据存储,在区域中处理并在出口处释放(设置为 0)。不是一个位(0/1)。但这并没有改变本质
    • lock bts 可能会更慢,尽管可能不是立即操作数。 (非锁定的bts 使用内存操作数很糟糕,尤其是mem,reg 而不是mem,imm,因为疯狂的CISC 位串索引)。 xchg mem,reg 可能是最好的,并且测试 reg 结果很便宜,例如 in my example asm implementation 但无论哪种方式,您都想在该缓存行上使用锁定操作之前检查锁定是否为只读可用,然后经常成功;不要只使用lock cmpxchglock btsxchg
    • mov [gf], 0 在 asm 中就足够了,但在 C / C++ 中 gf = 0 并不是因为编译时重新排序。 preshing.com/20120625/memory-ordering-at-compile-time
    • mov [gf], 0 在 asm 中就足够了 - 这对我来说很重要。我知道可能的 C/C++ 编译器重新排序,这对我来说不是问题 - 在 gf = 0 存在外部 api 调用之前和之后 - 任何编译器都不会重新排序此操作,因为这会中断程序逻辑
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-26
    • 2013-04-07
    • 2021-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多