BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Parallel and Asynchronous
.NET 1.1+

Thread-Safety with the Interlocked Class

When developing multi-threaded software or using parallel programming techniques, it is essential that classes remain thread-safe. The Interlocked class provides methods that assist with thread safety by performing atomic operations.

Thread-Safety

When you are using the task parallel library to parallelise your code, or when writing multi-threaded applications, you must ensure that your code is thread-safe. This means that any data that is shared by multiple threads must be accessed in a manner that ensures that threads cannot interfere with each other's results.

Some standard operations provided by C# and the .NET framework are not thread-safe, even though they may appear to be. For example, you might assume that the increment operator (++) performs an atomic operation. However, in reality incrementing a variable's value involves reading the value, updating the read value and writing the updated value back into the variable. If two threads attempt to increment a value simultaneously a race condition may occur and it is possible that the value be increased only by one, rather than two.

To demonstrate this problem, and for the other examples in this article, we will use methods from the Parallel class. To begin, create a console application and add the following using directives to the code. NB: If you are using an earlier version of the .NET framework than 4.0, you can create similar examples using standard multi-threading code.

using System.Threading;
using System.Threading.Tasks;

The following code executes a parallel for loop with one million increment operations, each changing the value of a shared variable. The final value of the variable should be one million. However, on a computer with multiple cores or processors, you will likely see a much lower result, caused by the increment operator not being thread-safe.

int total = 0;
Parallel.For(0, 1000000, i =>
{
    total++;
});
Console.WriteLine(total);   // Unlikely to be 1,000,000

Interlocked Class

The Interlocked class provides a number of static methods that perform atomic operations. These can generally be regarded as thread-safe. The class is found in the System.Threading namespace.

Increment and Decrement

Two commonly used Interlocked methods are Increment and Decrement. As their names suggest, these methods increase or decrease a variable's value by one, in a similar manner to the increment (++) and decrement (--) operators. Both methods work with either a 32-bit or 64-bit integer, which is passed to the method using a reference parameter.

The code below shows a thread-safe version of the earlier example. In this parallel loop the million operations produce the correct result.

int total = 0;
Parallel.For(0, 1000000, i =>
{
    Interlocked.Increment(ref total);
});
Console.WriteLine(total);   // 1,000,000

Add

The Add method was introduced with the .NET framework version 2.0. It performs an atomic, thread-safe addition of two values. The first parameter accepts a 32-bit or 64-bit integer variable, passed by reference. The second parameter accepts a second integer, which will be added to the first.

The sample below shows the Add method being used successfully within a parallel loop.

int total = 0;
Parallel.For(0, 1000000, i =>
{
    Interlocked.Add(ref total, 5);
});
Console.WriteLine(total);   // 5,000,000

Exchange

The Exchange method originally worked with 32-bit integers, single-precision floating-point numbers or objects. This was extended to a greater choice of data types, including generic types, with .NET 2.0. The method accepts two arguments of the same type. The first, passed by reference, is changed to match that of the second and the original value is returned.

The code below shows the syntax of the method and some sample results.

int value = 1;
int newValue = 2;

int oldValue = Interlocked.Exchange(ref value, newValue);

Console.WriteLine(value);       // 2
Console.WriteLine(oldValue);    // 1

CompareExchange

CompareExchange provides a similar operation to Exchange, in that it can replace one variable's value with an alternative value as an atomic operation. The difference is that the exchange is only made if a comparison with a third value determines equality.

The first parameter is the value that may be replaced. It is passed by reference to allow the original variable to be updated. The second parameter holds the value that the first may be replaced with. The third argument specifies a value to be compared with that of the first. If the two values match, the exchange is made. If not, the original value remains. In either case, the return value of the method is the original value of the first parameter.

The sample code below shows the CompareExchange method being called twice. In the first instance the comparison yields a match so the original value is replaced. In the second call the values do not match so the primary value remains the same.

int value = 1;
int compare = 1;
int compare2 = 5;
int newValue = 99;

int oldValue = Interlocked.CompareExchange(ref value, newValue, compare);
Console.WriteLine(value);       // 99 - changed
Console.WriteLine(oldValue);    // 1

value = 1;
oldValue = Interlocked.CompareExchange(ref value, newValue, compare2);
Console.WriteLine(value);       // 1 - unchanged
Console.WriteLine(oldValue);    // 1
29 February 2012