In the basic thread safety post we looked shortly at atomicity. In this post we want to look at how we can convert simple non atomic operations to atomic operations in the sense of a thread safe processor instruction. The key to do this in .Net is the static Interlocked class.

Our first example will be to make the the increment operation (++ operator) atomic. This is simply achieved by using the Interlocked.Increment method:

public static void Increment(ref int value, int iterations)
{
for (int i = 0; i < iterations; i++)
{
Interlocked.Increment(ref value);
}
}

Issues

This creates two issues:

  1. It can entail race conditions! (one thread called it, another directly after, the first thread than uses the value)
  2. It is significantly slower! Even though the locking per itself is in the ns area, it can grow slow if threads do content for the lock

Other operations

You also have the Interlock.Decrement which does the exact opposite of Interlocked.Increment (so the — operator), and the Interlocked.Add which simply adds one integer value to another in an atomic fashion. (all those methods are overloaded for double).

Interlocked.Exchange

This method is a thread safe assignment that returns the old value to the caller. The syntax is the following:

public static object Exchange(ref object location, object value)

Yet as we explored in Basic Thread Safety, an assignment operation in .Net is always an atomic operation because of the bit-nes of references.

So what good does it do then? One interesting idea is to create a simple SpinLock primitive. A SpinLock allows for spin wait which means the thread idles as long as it waits. This is important, because in this state it does not loose its alloted time slice, which makes it much cheaper than the use of Monitor/lock or any other synchronization technique in highly concurrent scenarios. This is because the threads do not need to wait as long as they might if they were waiting to get some processor time from the OS.

Although there is a SpinLock implementation in the System.Threading namespace, you can see how this can be implemented in an easy way with Interlocked.

public struct SpinLock
{
private int _lock;
public void Lock()
{
while (Interlocked.Exchange(ref _lock, 1) != 0);
}
public void UnLock()
{
_lock = 0;
}
}

Because Interlocked.Exchange returns the old value, and a thread t1 calls the Spinlock.Lock method, the value returned is 0, it also returns directly. Yet now the Value of the _lock field is 1.

Another thread t2 now calls the lock and waits until the first thread t1 calls Unlock which sets the _lock field value to 0 again. This works very well in highly concurrent systems with short locks.

Interlocked.ExchangeCompare

The CompareExchange with the following syntax is quite similar:

public static T CompareExchange<T>(ref T location, T value, T comparand)
where T : class

It compares the location and comparand. If they are the same, it assigns the value parameter to the location variable. The method then returns the original value of location. The Assertion of same here refers to referential equality.

So what is this good for? This method performs an exchange only if the reference is the same, instead of with the Interlocked.Exchange method which always does exchange the values. Note the generic class constraint, which means you either need to box your value types or convert them in any other fashion to use with this method.

Both methods do make a read and write to the same location atomic though (which is what we wanted to achieve).

Summary

To make nonatomic operations to atomic ones, we use the Interlocked static class that offers the Increment, Decrement, Add methods to add integers and doubles.

To assign values in an atomic operation, we can use the Interlocked.Exchange and Interlocked.CompareExchange methods.

Categories: TPL

0 Comments

Leave a Reply

Avatar placeholder