Using Multithreaded Counters Safely

Have you ever had multiple threads running all trying to read and update a counter variable? Has that variable not always been what you expected? If yes you are probably not using any type of locking to guard that variable from being updated in one thread at the exact same time another thread is trying to update it as well. There are two main ways of updating a counter variable safely and ensuring that a value does not get missed or overwritten in C#. Those two ways are using the lock keyword or using the Interlocked class in .Net.

Keep in mind that incrementing  involves three actions:

  • Read, in which a variable is accessed from memory.
  • Increment, in which the value is increased.
  • Write, where the incremented value is written back to memory.

If multiple threads are trying to do this all at once there can be trouble. If thread A is in the increment step at the same time thread B is at the read step, then when A writes its value B will overwrite it with the exact same value. Two increments will only be seen as one in this scenario.

Lock

The first way is to have some code such as the following:

object myLock = new object();
int counter = 0;
        
public void ThreadMethod()
{
        lock(myLock)
    {
        counter++;
    }
}

Here we create an object, myLock, which is the object we are going to lock on, this is like the bathroom key at a gas station, only the current holder of the key can use the bathroom at that given time. The counter variable will hold the current count. We have a method, ThreadMethod, which is going to be run by multiple threads all at once, pretend some actual work gets done in there as well. Inside this method we “lock” on the myLock object which means only one thread is allowed inside the lock block at any given moment. As soon as one thread exists the lock block, another thread may enter. This insures that only a single thread is able to increment the counter variable at a time.

This method works fine but there are a few things to consider.

  • Using a lock is slow because only one thread can be inside the lock at once.
  • You have to remember to use the lock everywhere the variable is accessed.
  • If the lock object is reused for other variables they will all wait on the unrelated operations. Use a new lock object per variable.
  • Lock is great if you are doing more than just incrementing a variable and the code inside is more complex.

Interlocked

System.Threading.Interlocked is a faster alternative to the previous use of lock. It is good for incrementing and decrementing a counter and not for more complex locking situations.

int counter = 0;
        
public void ThreadMethod()
{
    Interlocked.Increment(ref counter);
}

This sample is doing the same thing as the lock sample, only now it is does not need the lock object. Here the Interlocked.Increment method takes a reference to the counter variable and increments it in a single, atomic CPU instruction. That means that the read, increment, and write are being done all at once and therefore multiple threads will not step on each other when trying to increment the counter variable. Since this is an atomic operation there is no possibility of a context switch during the increment.

I was curios as to how this was being done so I found the .Net code on github. All the related methods for incrementing boiled down to this:

internal static extern int ExchangeAdd(ref int location1, int value);

That is helpful isn’t it? I had never seen the extern keyword before and this ExchangeAdd method is just declared, with no body. After a little research I found a few things out. According to the C# specification 10.6.7 (word doc):

When a method declaration includes an extern modifier, that method is said to be an external method. External methods are implemented externally, typically using a language other than C#. Because an external method declaration provides no actual implementation, the method-body of an external method simply consists of a semicolon. An external method may not be generic. The extern modifier is typically used in conjunction with a DllImport attribute, allowing external methods to be implemented by DLLs (Dynamic Link Libraries). The execution environment may support other mechanisms whereby implementations of external methods can be provided. When an external method includes a DllImport attribute, the method declaration must also include a static modifier.

So the extern keyword simply means the method is written in unmanaged code and gets filled in by the compiler. This can allow for lower level code to be used, in this case it boils down to one cpu instruction.

Summary

Both methods help keep our counters with the correct value and allow many threads to read, increment, and write all at nearly the same instant. Lock works for this case, but in more complex scenarios where more code is need lock really shines. Using the Interlocked class is much faster and cleaner when doing simple increment and decrement operations on a counter variable.

Let me know your thoughts and experiences with multithread counters, in the comments or on twitter @ScottKerlagon!