2

The .Net System.Threading.Interlocked class is primarily intended to offer the features of the ASM prefix LOCK.

However, I am scratching my head wondering why Microsoft limited the number of supported commands. I thought it was a "C#" issue, but a quick look on MSDN corrected that idea. It's a .Net limitation.

For example, there is no Interlocked.Or, yet in ASM one can say loosely LOCK OR eax,ebx. So why not Interlocked.Or(ref x, 10).

This makes no sense to me. Why no equivalent of LOCK OR but they do have LOCK ADD. The former is incredibly useful.

So I checked GCC and sure enough, it’s all there. Every single compatible LOCK command: GCC atomic commands

Can anyone shed some light on why MS would have omitted the majority of the x86 LOCK enabled commands?

pnuts
  • 58,317
  • 11
  • 87
  • 139
IamIC
  • 17,747
  • 20
  • 91
  • 154

2 Answers2

3

The .Net System.Threading.Interlocked class is primarily intended to offer the features of the ASM prefix LOCK.

No, it's primarily intended to offer atomic operations on variables, including memory barriers.

From the documentation:

The methods of this class help protect against errors that can occur when the scheduler switches contexts while a thread is updating a variable that can be accessed by other threads, or when two threads are executing concurrently on separate processors. The members of this class do not throw exceptions.

Now it so happens that the LOCK instruction also offers those facilities, but there's no explicit attempt to mimic all the functionality of the LOCK instruction. If that had been the intention, don't you think the instruction would have been mentioned?

I suspect that the API designers decided that they would offer the most commonly-required atomic operations - rather than looking at the LOCK instruction and deciding what to leave out.

Just like language features, API features have a cost associated with them, and each needs to earn its place. Interlocked is a pretty rarely-used class anyway - I suspect the number of developers requiring Interlocked.Or would be extremely small indeed.

Also, remember that .NET doesn't only run on x86. I have no idea whether the instructions in the Windows Phone 7 processors (which I'd guess are ARM) have the same facilities as the x86 LOCK instruction, for example.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • with all due respect, that's a play on semantics. LOCK is designed to do exactly what Interlocked is attempting, even per the definition given. It still would have made more sense to include the "missing" LOCK instructions. In my opinion, anyway, since the missing instructions are useful. – IamIC Apr 09 '11 at 19:09
  • @IanC: No, it's an extremely important distinction. Ask a C# language designer why C# is "missing" a feature that C++ has, and you'll get a similar response. Features should be considered as having to earn there place, rather than being "missing" if a similar API (in this case assembly) happens to have them. If you'd merely asked the question "Why did the .NET designers not consider an atomic OR method important enough to include in the Interlocked class?" that would have been better, IMO. And the answer is the same: I doubt that enough people require it. – Jon Skeet Apr 09 '11 at 19:12
  • I suspect the second part of your answer is the real one: possible lack of non-Intel support (that would be easy to verify) but, more likely, the platform isn't designed for low-level coding like that. (I know you didn't word it like that.) – IamIC Apr 09 '11 at 19:13
  • Out of interest, I looked up the Windows Phone, and it does use an ARM CPU, and it appears to only support a locked swap. This doesn't explain how Add would run. – IamIC Apr 09 '11 at 19:18
  • 1
    @IanC: All the interlocked primitives other than `CompareExchange` may be emulated on a platform which can perform, at minimum, a compare-and-swap operation (the latter, which would be better called "comare-and-store", acts like `CompareExchange` except that the return value simply indicates whether the store succeeded, rather than returning the value that caused its unexpected failure). For e.g. atomic add, one would read the value, compute the new value, attempt to compare-and-swap the old value with the new value, and reloop if the compare-and-swap fails. – supercat Aug 17 '12 at 00:05
  • @IanC: I suspect much of the reason that .net doesn't offer more interlocked methods is that `CompareExchange` can be used to emulate them (note that `CompareExchange` itself cannot be emulated perfectly by compare-and-swap, since an attempt to read data following a failed compare-and-swap might not yield the data which was stored at the time the compare failed. – supercat Aug 17 '12 at 00:08
0
/// <summary>
/// .NET: atomically set a bit in a 32-bit integer in shared memory
/// </summary>
/// <param name="status">memory location at which to atomically set a single bit</param>
/// <param name="singleton_bit">should have only one bit set (i.e. a power of two);
/// otherwise it is not possible to tell which caller enacted the change.</param>
/// <returns>
/// Returns the previous value at <paramref name="pi"/>. If the indicated bit is cleared 
/// in this return value then this caller enacted the change. If the indicated bit is set 
/// in the return value then no action was taken.
public static int InterlockedSetBit(ref int pi, int singleton_bit)
{
    Debug.Assert((singleton_bit & (singleton_bit - 1)) == 0);
    int _cur = pi;
    do
        if ((_cur & singleton_bit) != 0)
            return _cur;
    while (_cur != (_cur = Interlocked.CompareExchange(ref pi, _cur ^ singleton_bit, _cur)));
    return _cur;
}

/// <summary>
/// .NET: atomically clear a bit in a 32-bit integer in shared memory
/// </summary>
/// <param name="status">memory location at which to atomically clear a single bit</param>
/// <param name="singleton_bit">should have only one bit set (i.e. a power of two);
/// otherwise it is not possible to tell which caller the change.</param>
/// <returns>
/// Returns the previous value at <paramref name="pi"/>. If the indicated bit is set 
/// in this return value then this caller enacted the change. If the indicated bit is clear 
/// in the return value then no action was taken.
public static int InterlockedClearBit(ref int pi, int singleton_bit)
{
    Debug.Assert((singleton_bit & (singleton_bit - 1)) == 0);
    int _cur = pi;
    do
        if ((_cur & singleton_bit) == 0)
            return _cur;
    while (_cur != (_cur = Interlocked.CompareExchange(ref pi, _cur ^ singleton_bit, _cur)));
    return _cur;
}
Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108