Developers are now having to deal with concurrency issues far more than we ever have in the past. Our languages are evolving more and more features (like the Parrallel.* class in C#) that allow us to take advantage without too much pain.
These are great, but they’re not always the answer and they don’t negate the need to understand the fundamental issues of concurrent programming. This is in fact the very subject that this blog started with several years ago...
Today I’d like to talk about Interlocked.Exchange. Its purpose it to exchange two variables in an atomic way. It’s the atomic nature of the exchange that’s the important factor here, it means that nothing can catch the exchange in an incomplete state. It’s either completely one value or completely another value.
Thread Safe Exchange
The significance of this becomes evident when we consider the traditional method of exchanging two variables.
var spare = first; first = second; second = spare;
This is not thread safe. The problem is that a second thread could interleave into the first. Consider the following path of execution. Let’s assume the value of first is 5 and the value of second is 10. The table shows the values of the variables as two threads interleave.
Thread | Code | first | second | spare 1 | spare 2 |
---|---|---|---|---|---|
1 | var spare = first; | 05 | 10 | 05 | |
1 | first = second; | 10 | 10 | 05 | |
2 | var spare = first; | 10 | 10 | 05 | 10 |
1 | second = spare; | 10 | 05 | 05 | 10 |
2 | first = second; | 05 | 10 | 05 | 10 |
2 | second = spare; | 05 | 10 | 05 | 10 |
Oh dear… that didn’t work, did it?
If we’d used Interlocked.Exchange we wouldn’t have the problem, because the exchange is atomic no interleaving can take place, the first thread will finish then the second will take over.
Thread Safe Changes of Scope
When it gets really interesting though is the fact that Interlocked.Exchange returns the original value.
public static T Exchange( ref T location1, T value) where T : class
The beauty of this is that we can use this to change scope. There’s a wholly unsafe pattern that gets used in Dispose handlers all the time:
class Blah: IDisposable { SomeDisposableType someObject; ... public void Dispose() { if(someObject != null) { someObject.Dispose(); someObject = null; } } }
It’s possible that two threads could interleave between the null check and someObject being assigned to null, resulting in someObject being disposed twice.
Alternatively…
public void Dispose() { var myCopy = Interlocked.Exchange(ref someObject, null); if(myCopy != null) { myCopy.Dispose(); } }
What the Interlocked.Exchange does here is to set someObject to null in the object scope and return its (former) value to myCopy which is in the method scope.
If two threads call Dispose at exactly the same moment, then both of them will succeed in setting the value of someObject to null. In the case of the thread that calls Interlocked.Exchange first, it will return the original value of someObject to its myCopy and set someObject to null. When the second thread calls Interlocked.Exchange it will return the value that the first thread set someObject to, that being null. It will then proceed to set someObject to null again.
The effective is that someObject is set to null twice, but only one of the threads gets the original value of someObject, so only one will pass the null check and only one will call Dispose on someObject.
Note that this isn’t a good Dispose pattern fullstop however. Microsoft have written some guidelines.
This scope switching trick can be useful in other places too. Consider if you’re writing a log file of some description. Writing an ever-expanding log file causes problems, it’s good to have a cut-off and write a new one every so often. A common method is to write one file per day of the week.
public class LogWriter : IDsiposable { StreamWriter writer; public string FilePath {get;set;} ... public void WriteLog(string s) { writer.WriteLine(s); } public void StartNewDaysLog() { var newWriter = new StreamWriter(FilePath + DateTime.Now.DayOfWeek.ToString() + ".log", false); var oldWriter = Interlocked.Exchange(ref writer, newWriter); if(null != oldWriter) oldWriter.Close(); } ... }
With this implementation multiple threads can safely call WriteLog continuously. When StartNewDaysLog is called a new StreamWriter is set up ready to go, then the two are switched in an atomic fashion. Nothing can catch this out half way through the switch – as far as anything calling WriteLog is concerned one entry was written to one file and the next to another: it’s seamless.
After the switch, StartNewDaysLog is left with the old StreamWriter which it then has to Close (which in turn calls Dispose).
Conclusion
Interlocked.Exchange is a surprisingly useful little tool. Its plain usage – to simply exchange a value in a thread safe way is useful, but where it really comes in handy is in its ability to replace a value and return the original one into a narrower (thread safe) scope. This is particularly handy if you need to move or consume something in a simple way that doesn’t imply graduating to a mechanism such as ReaderWriterLockSlim or SemaphoreSlim.