Interlocked
This C# class
helps with threading. It safely changes the value of a shared variable from multiple threads. It is part of System.Threading
.
Using threads safely is possible with the lock statement. But you can instead use the Interlocked
type for simpler and faster code.
Interlocked.Add
First, forget about the addition, subtraction and assignment operators. Instead, you will use the Add, Increment, Decrement, Exchange and CompareExchange
methods.
Interlocked
and Interlocked.Add
.Interlocked
, both threads could read the value, then both change it afterwards. The result would be 1 not 2.using System; using System.Threading; class Program { static int _value; static void Main() { Thread thread1 = new Thread(new ThreadStart(A)); Thread thread2 = new Thread(new ThreadStart(A)); thread1.Start(); thread2.Start(); thread1.Join(); thread2.Join(); Console.WriteLine(Program._value); } static void A() { // Add one. Interlocked.Add(ref Program._value, 1); } }2
Continuing on, the Interlocked
type offers the Increment and Decrement methods as well. These methods are convenient, and they add or subtract 1.
using System; using System.Threading; class Program { static int _value; static void Main() { Thread thread1 = new Thread(new ThreadStart(A)); Thread thread2 = new Thread(new ThreadStart(A)); thread1.Start(); thread2.Start(); thread1.Join(); thread2.Join(); Console.WriteLine(Program._value); } static void A() { // Add one then subtract two. Interlocked.Increment(ref Program._value); Interlocked.Decrement(ref Program._value); Interlocked.Decrement(ref Program._value); } }-2
Here we see how the Interlocked.Exchange
method works. Exchange is essentially an assignment: the value we pass to it is changed to the argument.
using System; using System.Threading; class Program { static long _value1; static void Main() { Thread thread1 = new Thread(new ThreadStart(A)); thread1.Start(); thread1.Join(); // Written [2] Console.WriteLine(Interlocked.Read(ref Program._value1)); } static void A() { // Replace value with 10. Interlocked.Exchange(ref Program._value1, 10); // CompareExchange: if 10, change to 20. long result = Interlocked.CompareExchange(ref Program._value1, 20, 10); // Returns original value from CompareExchange [1] Console.WriteLine(result); } }10 20
While calling Interlocked
methods seems simpler in programs, does it actually perform faster than a lock? In this benchmark we time the 2 approaches in C#.
Interlocked
.Interlocked.Increment
in the second loop.Interlocked.Increment
was several times faster, requiring only 6 nanoseconds versus 40 nanoseconds for the lock construct.using System; using System.Diagnostics; using System.Threading; class Program { static object _locker = new object(); static int _test; const int _max = 10000000; static void Main() { var s1 = Stopwatch.StartNew(); // Version 1: use lock. for (int i = 0; i < _max; i++) { lock (_locker) { _test++; } } s1.Stop(); var s2 = Stopwatch.StartNew(); // Version 2: use Interlocked. for (int i = 0; i < _max; i++) { Interlocked.Increment(ref _test); } s2.Stop(); Console.WriteLine(_test); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); } }20000000 40.02 ns 6.40 ns [Interlocked.Increment]
Once you get past the awkward syntax of the Interlocked
methods, they can make writing multithreaded C# programs easier. You can avoid locking on certain values.