views:

96

answers:

2

I have multiple threads accessing variables. I know how to write spinlocks and use the Threading.Interlocked methods to increment etc. variables.

However, I want to perform the equivalent of:

a = Math.Min(a, b)
or
a = a | 10

... but without using a critical section. Is this possible? I know the 2nd line is possible in assembler, but there is no Interlocked.Or method.

+2  A: 

If you don't anticipate a great deal of contention then perhaps something like this? (If there's likely to be a lot of contention then a plain lock might well be more efficient.)

int original;    // assuming here that a is an int
do
{
    original = a;
} while (Interlocked.CompareExchange(ref a, original | 10, original) != original)
LukeH
+2  A: 

Here is the general pattern for simulating an interlocked operation.

public static T InterlockedOperation<T>(ref T location, T value)
{
  T initial, computed;
  do
  {
    initial = location;
    computed = op(initial, value); // initial | value
  } 
  while (Interlocked.CompareExchange(ref location, computed, initial) != initial);
  return computed;
}

The min operation is a completely different story. The issue here is that there are two memory locations in play. Furthermore, we are only interested in reading them. That means we really only need to worry about the memory barrier problem. Decorate your fields with volatile or do an explicit call to Thread.MemoryBarrier prior to computing the min.

Edit: I missed the fact that the result of the min operation is assigned to a. You can actually use the pattern I defined above, but instead of doing computed = initial | value do computed = initial < value ? initial : value. Everything else stays the same.

Brian Gideon
I think your Thread.MemoryBarrier suggestion is the correct solution. The pattern suggested could still result in a miscalculation under contention.
IanC
@IanC: I'm not seeing how. If another thread set `a` to a smaller value using this pattern then the current thread will have to spin around the loop again right? I think the real problem is that the value of `b` that you pass to the `value` parameter could have changed even before the function starts, but from the function's perspective the computed value and the reassignment should be correct.
Brian Gideon
NOW I get what you mean! You are 100% correct. Thanks Brian!
IanC
@IanC: Just remember to do a volatile read of `b` when passing it to the `value` parameter otherwise the thread may get a stale read. So you still might have to use the `volatile` keyword or `Thread.MemoryBarrier` for that part.
Brian Gideon
@Brian volatile will do the trick there :)
IanC