Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I atomically swap 2 ints in C#?

Tags:

c#

.net

atomic

What (if any) is the C# equivalent of the x86 asm xchg instruction?

With that command, which imo is a genuine exchange (unlike Interlocked.Exchange), I could simply atomically swap two ints, which is what I am really trying to do.

Update:

Sample code based upon my suggestion. Variable suffixed "_V" are decorated as 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;

Note the lazy update.

like image 973
IamIC Avatar asked Oct 04 '10 13:10

IamIC


3 Answers

This is the likely implementation for Interlocked.Exchange() in the CLR, copied from the SSCLI20 source:

Note that UP in the function name means UniProcessor. This is not atomic on SMP / multi-core systems. This implementation will only be used by CLR on single-core systems.

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

It is superior to using XCHG because this code works without taking a bus lock. xchg has an implicit lock prefix, so unlike xadd or cmpxchg it simply can't be omitted for single-core systems to still do the operation in one instruction to make it atomic with respect to interrupts (and thus other threads on uniprocessor).

The odd looking jumping code is an optimization in case branch prediction data is not available. Needless to say perhaps, trying to do a better job than what has been mulled over for many years by very good software engineers with generous helpings from the chip manufacturers is a tall task.

like image 114
Hans Passant Avatar answered Oct 22 '22 10:10

Hans Passant


Here's kind of a weird idea. I don't know exactly how you have your data structure set up. But if it's possible you could store your two int values in a long, then I think you could swap them atomically.

For example, let's say you wrapped your two values in the following manner:

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);
    }
}

Then it seems you could add the following Swap method to result in a swapped pair "atomically" (actually, I don't know if it's fair to really say that the following is atomic; it's more like it produces the same result as an atomic swap).

/// <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);
}

It is highly possible I've implemented this incorrectly, of course. And there could be a flaw in this idea. It's just an idea.

like image 33
Dan Tao Avatar answered Oct 22 '22 08:10

Dan Tao


Why isn't Interlocked.Exchange suitable for you?

If you require the exact memory locations to be swapped then you're using the wrong language and platform as .NET abstracts the memory management away so that you don't need to think about it.

If you must do something like this without Interlocked.Exchange, you could write some code marked as unsafe and do a traditional pointer-based swap as you might in C or C++, but you'd need to wrap it in a suitable synchronisation context so that it is an atomic operation.

Update
You don't need to resort to unsafe code to do a swap atomically. You can wrap the code in a synchronisation context to make it atomic.

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

Update 2
If synchronisation is not an option (as indicated in the comments), then I believe you're out of luck. As you're chasing some unmeasured efficiency, you may want to concentrate elsewhere. If the swapping of two integer values is a huge performance hog, you're probably using the wrong platform.

like image 24
Jeff Yates Avatar answered Oct 22 '22 10:10

Jeff Yates