【问题标题】:How do I atomically swap 2 ints in C#?如何在 C# 中以原子方式交换 2 个整数?
【发布时间】:2011-04-20 19:38:50
【问题描述】:

x86 asm xchg 指令的 C# 等效项是什么(如果有)?

使用该命令,imo 是一个真正的交换(不像Interlocked.Exchange),我可以简单地原子交换两个整数,这是我真正想要做的。

更新:

基于我的建议的示例代码。变量后缀“_V”被修饰为 volatile:

// PART 3 - process links
// prepare the new Producer
address.ProducerNew.WorkMask_V = 0;
// copy the current LinkMask
address.ProducerNew.LinkMask_V = address.Producer.LinkMask_V;
// has another (any) thread indicated it dropped its message link from this thread?
if (this.routerEmptyMask[address.ID] != 0)
{
  // allow all other bits to remain on (i.e. turn off now defunct links)
  address.ProducerNew.LinkMask_V &= ~this.routerEmptyMask[address.ID];
  // reset
  this.routerEmptyMask[address.ID] = 0;
}
// PART 4 - swap
address.ProducerNew = Interlocked.Exchange<IPC.Producer>(ref address.Producer, address.ProducerNew);
// PART 5 - lazily include the new links, make a working copy
workMask = address.Producer.LinkMask_V |= address.ProducerNew.WorkMask_V;

注意惰性更新。

【问题讨论】:

  • 请使用您在 cmets 中为我的回答提供的其他详细信息更新您的问题。如果其他人知道所有限制条件,这将有助于其他人尝试回答您的问题。 stackoverflow.com/questions/3855671/…
  • XCHG 指令不是您要查找的原子操作,因为没有XCHG [mem1],[mem2] 操作码。你需要三个指令:mov reg,[mem1] XCHG reg,[mem2] mov [mem1],reg
  • @Skizz 我又看了一下 ASM 手册 - 你是对的。哇。即使在汇编程序中也无法完成我正在寻找的工作。
  • 实际上,在任何语言中,即使是 ASM,都不可能原子地交换两个变量。只能以原子方式将 a 与 b 交换,而非原子地得到 a 作为结果。
  • @GlennSlayden:m68k 有/有双字比较和交换(内存中两个不连续的字对两个寄存器)。 en.wikipedia.org/wiki/Double_compare-and-swap。在现代英特尔上,您可以使用 TSX(事务性内存)来做到这一点。

标签: c# .net atomic


【解决方案1】:

这是 CLR 中 Interlocked.Exchange() 的可能实现,从 SSCLI20 源复制:

请注意,函数名称中的 UP 表示 UniProcessor。这在 SMP / 多核系统上不是原子的。此实现仅由 CLR 在单核系统上使用。

FASTCALL_FUNC ExchangeUP,8
        _ASSERT_ALIGNED_4_X86 ecx
        mov     eax, [ecx]      ; attempted comparand
retry:
        cmpxchg [ecx], edx
        jne     retry1          ; predicted NOT taken
        retn
retry1:
        jmp     retry
FASTCALL_ENDFUNC ExchangeUP

它优于使用 XCHG,因为此代码无需总线锁即可工作。 xchg 有一个隐含的 lock 前缀,因此与 xaddcmpxchg 不同,它根本不能省略,单核系统仍然在一条指令中执行操作以使其相对于中断具有原子性(和因此单处理器上的其他线程)。

奇怪的跳转代码是在分支预测数据不可用的情况下进行的优化。毋庸置疑,在芯片制造商的慷慨帮助下,试图做得比优秀的软件工程师多年来深思熟虑的工作做得更好是一项艰巨的任务。

【讨论】:

  • 确实如此。无疑。相当。 +1
  • 如果分支预测数据不可用,为什么跳转会有帮助?
  • @the - 如果它不可用,那么处理器必须猜测。它总是猜测“未采取跳跃”。这段代码很可能是正确的。如果使用了 je 指令,很可能是错误的。由于管道,猜错非常昂贵。
  • @HansPassant 在最后的帐户中,我能够编写一个阻塞的线程间消息传递系统,它的运行速度比为此所需的内置 .Net 4.0 类型快 9.1 倍。该代码还做了更多(路由),因此从技术上讲它要快一个数量级。所以有时候,在芯片制造商的慷慨帮助下,非常优秀的软件工程师多年来一直在思考的东西,有时不仅可以打败,而且可以粉碎它:)
  • @IanC:在谈到“9.1 倍”的改进时,您能描述一下您实际比较的是哪两件事吗?特别是因为您接受的答案实际上使用了Interlocked?
【解决方案2】:

这是一个奇怪的想法。我不知道您是如何设置数据结构的。但是,如果您可以将两个int 值存储在long 中,那么我认为您可以原子地交换它们。

例如,假设您以下列方式包装了两个值:

class SwappablePair
{
    long m_pair;

    public SwappablePair(int x, int y)
    {
        m_pair = ((long)x << 32) | (uint)y;
    }

    /// <summary>
    /// Reads the values of X and Y atomically.
    /// </summary>
    public void GetValues(out int x, out int y)
    {
        long current = Interlocked.Read(ref m_pair);

        x = (int)(current >> 32);
        y = (int)(current & 0xffffffff);
    }

    /// <summary>
    /// Sets the values of X and Y atomically.
    /// </summary>
    public void SetValues(int x, int y)
    {
        // If you wanted, you could also take the return value here
        // and set two out int parameters to indicate what the previous
        // values were.
        Interlocked.Exchange(ref m_pair, ((long)x << 32) | (uint)y);
    }
}

那么您似乎可以添加以下 Swap 方法以“原子地”产生一对交换对(实际上,我不知道是否真的可以说以下 is 原子; 它更像是产生与原子交换相同的结果)。

/// <summary>
/// Swaps the values of X and Y atomically.
/// </summary>
public void Swap()
{
    long orig, swapped;
    do
    {
        orig = Interlocked.Read(ref m_pair);
        swapped = orig << 32 | (uint)(orig >> 32);
    } while (Interlocked.CompareExchange(ref m_pair, swapped, orig) != orig);
}

当然,我很可能错误地实现了这一点。这个想法可能存在缺陷。这只是一个想法。

【讨论】:

  • 该死的,为什么我没有想到将 2 个 int 存储在 long 中!不错!
  • 该技术是从 ABA 问题的解决方案中得知的,您在高 32 位中保留递增的“版本”计数器,在低 32 位中保留数据,以检测 ABA 故障。请参阅 Duffy 的“Windows 上的并发编程”的第 536 页。我写了一个基于 64 位原子操作的托管无锁字典,完全像这样在成对的 32 位邻居上完成。
  • @dantao 这个想法确实有效。事实上,我设法将 3 个数字放入一个 long 中,并使用轮班控制它们。这是完美的解决方案。
  • @IanC:太棒了!通常,我回顾两年前发布给 SO 的答案时会感到畏缩;但很高兴知道这个想法最终证明对您有用(尽管我确信您必须进行重大更改才能将其应用到您的场景中)。
  • @DanTao 将两个值绑定在一起的另一个有用功能是它们可以在一个操作中进行操作,例如增加一个和减少另一个。这在应用中非常实用。
【解决方案3】:

为什么Interlocked.Exchange 不适合你?

如果您需要交换确切的内存位置,那么您使用的是错误的语言和平台,因为 .NET 将内存管理抽象出来,因此您无需考虑它。

如果你必须在没有Interlocked.Exchange 的情况下做这样的事情,你可以编写一些标记为unsafe 的代码,并像在 C 或 C++ 中那样进行传统的基于指针的交换,但你需要将它包装在合适的同步上下文,使其成为原子操作。

更新
您无需求助于unsafe 代码即可原子地进行交换。您可以将代码包装在同步上下文中以使其具有原子性。

lock (myLockObject)
{
  var x = Interlocked.Exchange(a, b);
  Interlocked.Exchange(b, x);
}

更新 2
如果同步不是一个选项(如 cmets 所示),那么我相信你不走运。当你追求一些无法衡量的效率时,你可能想把注意力集中在其他地方。如果两个整数值的交换是一个巨大的性能消耗,你可能使用了错误的平台。

【讨论】:

  • @IanC:为什么锁不合适?也许如果您对所有细节更加坦诚,您可以获得答案。在我们进行过程中添加额外的约束是没有帮助的。
  • @IanC:你怎么知道它会更有效率?在我看来,你正在抓着稻草。请张贴证据。
  • @Jeff 你是对的。 .Net 不是满足这一要求的最佳平台。但它非常适合项目的其余部分。现在,我将不得不坚持我当前的代码,看看效果如何。
  • @Matt:Interlocked.Exchange 是原子的,但对 b 的赋值也不是。
  • 嗯,我这里的锁有问题 - 它不会使代码块内的语句本身原子性 - 只有原子性来自于在其他地方拥有相同的锁 - 访问没有它的变量会像那样破坏那个原子......
【解决方案4】:

Interlocked.Exchange 真的是你唯一能做的:

  var x = Interlocked.Exchange(a, b);
  Interlocked.Exchange(b, x);

您是正确的,这不会是原子的,但是使用局部变量,只要两行都执行,就可以保证值是一致的。您的其他选项是不安全代码(用于使用指针)、使用 p/invoke 到本机库,或重新设计以使其不再需要。

【讨论】:

  • 它必须是原子的。您能否为我提供有关如何使用不安全代码执行此操作的参考?
  • Interlocked.Exchange 是原子操作,但是,连续 2 次并非没有同步。如 MSDN 文档中所述 - msdn.microsoft.com/en-us/library/…
  • @IanC unsafe c# 不会让它比这更更多原子。如果你真的,真的需要 XCHG,你必须自己用 c 写。不过,不确定过渡是否会给您带来比纤细锁更大的性能优势。看到这样的东西:codeproject.com/KB/cs/unmanage.aspx
  • @Jeff Yates 确实连续 2 个不是原子的 - 这就是我的观点。他想要两个值的实际交换,而不仅仅是一个值的变化,我是说这在纯c#中尽可能接近
【解决方案5】:

根据MSDN,Interlocked.Exchange 原子的。

如果它不适合您,您可以使用 C/C++ 在 unsafe 部分中实现 XCHG。

【讨论】:

  • 这似乎是唯一的解决方案。谢谢。
  • 没有锁,我也看不出不安全的代码会如何工作。如果没有某种同步,就无法使其原子化。
  • @Jeff Yates 你在原子部分是对的。我想说的是,在不安全的部分,你可以使用内联汇编来做 XCHG。
【解决方案6】:

Interlocked.Exchange 是在 c# 中以线程安全的方式交换两个 int 值的最佳方式。

使用这个类,即使您拥有多处理器计算机,但您永远不知道您的线程将在哪个处理器中运行。

【讨论】:

    【解决方案7】:

    我想我刚刚找到了最好的解决方案。它是:

    Interlocked.Exchange() 方法(参考 T,T)

    所有“新”变量都可以在一个类(Of T)中设置并与当前变量交换。这使得原子快照能够发生,同时有效地交换任意数量的变量。

    【讨论】:

    • 是的,我在这种方法中看到的唯一问题是,您突然必须将数据放入不可变引用类型中,这可能会过度,具体取决于您执行交换的频率。
    • @Dan,我认为这没有任何问题。显然,这只是交换指针。它不会影响写入对象的速度。
    • @IanC:我的意思是,如果我理解正确,您需要为每次交换创建一个新对象(例如 Interlocked.Exchange(pair, new Pair(pair.X, pair.是)))。也许这很好;只是看起来有点……重?或者我理解错了。
    • @Dan 我只需要创建 2 个对象并简单地不断交换指向它们的 2 个指针。一旦交换完成并读取数据,对象可能会被归零,然后将被读取以进行交换。你明白我的意思了吗?
    • @IanC:我想我假设您在询问原子交换,因为您正在处理多个线程。您刚才描述的想法的问题是您的“保留”对象可以由多个线程同时修改,然后交换具有无效值。您可以在不使用某种池的持续对象分配的情况下执行此操作,但对于本应是一个简单问题的问题,这最终可能过于复杂。
    【解决方案8】:

    在 Interlocked.Exchange 之外,我假设 XCHG 命令可能是 XOR Swap 的实现,因此您可以自己编写。

    C 语法:(来自维基百科链接)

     void xorSwap (int *x, int *y) {
         if (x != y) {
             *x ^= *y;
             *y ^= *x;
             *x ^= *y;
         }
     }
    

    编辑这不是原子的,你必须自己同步它

    【讨论】:

    • 根据 OP 的要求,这不是原子的。
    • XCHG 是一条 x86 CPU 指令,它是原子的,因为它不能被另一条指令中断。如果不先对参数添加额外的锁定,XOR 交换就不是原子的。
    • @Rup 啊,我不知道原子的要求。
    猜你喜欢
    • 2011-09-22
    • 1970-01-01
    • 2011-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-08
    • 1970-01-01
    相关资源
    最近更新 更多