Modifying a shared integer in a thread-safe manner in .NET
June 3, 2015 Leave a comment
The Interlocked class in the System.Threading namespace provides a number of useful methods to modify the value of an integer that is shared among multiple threads.
Consider the following code. It updates the same shared integer in a loop on 5 different threads and prints the final status at the end. 3 threads add 1 to the integer 600 times and 2 threads subtracts 1 600 times. Therefore we’re expecting the shared integer to be 600 at the end:
public class InterlockedSampleService { private int _sharedInteger; public void RunInterlockedSampleCode() { Task modifyTaskOne = Task.Run(() => ModifySharedIntegerInLoop(600, 1)); Task modifyTaskTwo = Task.Run(() => ModifySharedIntegerInLoop(600, -1)); Task modifyTaskThree = Task.Run(() => ModifySharedIntegerInLoop(600, 1)); Task modifyTaskFour = Task.Run(() => ModifySharedIntegerInLoop(600, -1)); Task modifyTaskFive = Task.Run(() => ModifySharedIntegerInLoop(600, 1)); Task.WaitAll(modifyTaskOne, modifyTaskTwo, modifyTaskThree, modifyTaskFour, modifyTaskFive); Debug.WriteLine(string.Format("Final value: {0}", _sharedInteger)); } private void ModifySharedIntegerInLoop(int maxLoops, int amount) { for (int i = 0; i < maxLoops; i++) { ModifySharedIntegerSingleTime(amount); } } private void ModifySharedIntegerSingleTime(int amount) { _sharedInteger += amount; } }
If you repeatedly run the RunInterlockedSampleCode method then you may get 600 most of the times. However, if you execute it say 5-10 times you should eventually see at least 1 incorrect result. I ran it 10 times and got the following incorrect results:
297
589
632
379
So it failed 4 times out of 10. You’ll certainly get different results.
The point of this exercise is that if multiple threads are allowed to modify the same shared integer then you can never be sure what the state of that integer is at the time a certain thread modifies it.
There are a couple of ways to solve it but let’s concentrate on the Interlocked class. It has a number of methods that modify integers and longs in an atomic way, i.e. in a single transaction where only one thread is allowed access to a resource.
We only need change the code a little bit:
private void ModifySharedIntegerSingleTime(int amount) { Interlocked.Add(ref _sharedInteger, amount); }
Now you can execute the code as often as you want, you’ll get 600 every time.
Interlocked has the following interesting methods too:
- Decrement: decrement the value of a number by one
- Increment: increment the value of a number by one
- Read: read the current value if a shared numeric field by one
View the list of posts on the Task Parallel Library here.