Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Interlocked.CompareExchange<T> only support reference types?

Disclaimer: My posts are apparently always verbose. If you happen to know the answer to the title question, feel free to just answer it without reading my extended discussion below.


The System.Threading.Interlocked class provides some very useful methods to assist in writing thread-safe code. One of the more complex methods is CompareExchange, which can be used for computing a running total that may be updated from multiple threads.

Since the use of CompareExchange is a bit tricky, I thought it a rather common-sense idea to provide some helper methods for it:

// code mangled so as not to require horizontal scrolling
// (on my monitor, anyway)
public static double Aggregate
(ref double value, Func<double, double> aggregator) {
    double initial, aggregated;

    do {
        initial = value;
        aggregated = aggregator(initial);
    } while (
        initial != Interlocked.CompareExchange(ref value, aggregated, initial)
    );

    return aggregated;
}

public static double Increase(ref double value, double amount) {
    return Aggregate(ref value, delegate(double d) { return d + amount; });
}

public static double Decrease(ref double value, double amount) {
    return Aggregate(ref value, delegate(double d) { return d - amount; });
}

Now, perhaps I am just guilty of being generic-happy (I will admit, this is often true); but it does feel silly to me to restrict the functionality provided by the above methods to double values only (or, more accurately, for me to have to write overloaded versions of the above methods for every type I want to support). Why can't I do this?

// the code mangling continues...
public static T Aggregate<T>
(ref T value, Func<T, T> aggregator) where T : IEquatable<T> {
    T initial, aggregated;

    do {
        initial = value;
        aggregated = aggregator(initial);
    } while (
        !initial.Equals(
            Interlocked.CompareExchange<T>(ref value, aggregated, initial)
        )
    );
}

I can't do this because Interlocked.CompareExchange<T> apparently has a where T : class constraint, and I don't understand why. I mean, maybe it's because there are already overloads for CompareExchange that accept Int32, Int64, Double, etc.; but that hardly seems a good rationale. In my case, for example, it would be quite handy to be able to use the Aggregate<T> method to perform a wide range of atomic calculations.

like image 656
Dan Tao Avatar asked Feb 11 '10 01:02

Dan Tao


People also ask

When should you use the Interlocked class?

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.

What is interlocked exchange?

Interlock. Exchange returns the original value while performing an atomic operation. The whole point is to provide a locking mechanism. So it is actually two operations: read original value and set new value.


2 Answers

Because that overload is specifically intended to compare and exchange a reference. It does not perform an equality check using the Equals() method. Since a value type would never have reference equality with the value you're comparing it against, my guess is that they constrained T to class in order to prevent misuse.

like image 33
Josh Avatar answered Nov 04 '22 01:11

Josh


Interlocked.CompareExchange is meant to be implemented with native atomic instructions provided directly by the processor. It's pointless to have something like that use a lock internally (it's designed for lock-free scenarios).

Processors that provide atomic compare exchange instruction naturally support it as small, "register-sized" operations (e.g. the largest compare-exchange instruction on an Intel x64 processor is cmpxchg16b that works on 128 bit values).

An arbitrary value type can be potentially bigger than that and compare-exchanging it may not be possible with a single instruction. Compare-exchanging a reference type is easy. Regardless of its total size in memory, you'll be comparing and copying a small pointer of a known size. This is also true for primitive types like Int32 and Double—all of them are small.

like image 161
mmx Avatar answered Nov 04 '22 00:11

mmx