Thread safety and the swap-reference pattern
True immutability for deep object hierarchies can only be achieved with deep copy, a requirement that is at best time consuming to achieve and at worst, not possible (when using third-party libraries, for example). A commonly-known mechanism for achieving thread safety in your application is the use of the lock keyword. I'm not going to spend too much time on this, I'm sure you know what it is. While the pattern is familiar and therefore fairly easy to use, there are other mechanisms for achieving thread safety.
Why bother, I hear you ask? I admit, it is applicable and flexible to cover a variety of scenarios. However, there are scenarios wherein alternative strategies could be considered, strategies that are faster and fairly easy to implement. One such strategy is the swap-reference pattern.
Scenario:
Let us say I have a simple set of cached data that I populate at the start of the application. The data changes infrequently, with a forced refresh on a schedule (e.g. daily):
public interface IReferenceCache<T> where T: class { //Get the latest version. T Get(); //Return the previous version. T Put(T t); } /// <summary> /// NOT THREADSAFE!!! /// </summary> /// <typeparam name="T">Any reference type.</typeparam> public class ReferenceCache<T>: IReferenceCache<T> where T:class { private T _objRef; public T Get() { return _objRef; } public T Put(T t) { T previous = _objRef; _objRef = t; return previous; } } public class CacheManager { private readonly IReferenceCache<MutableObject> _simpleObjectCache; private readonly IReferenceCache<List<MutableObject>> _listObjectCache; private readonly IReferenceCache< Dictionary<string, MutableObject>> _dictionaryCache; public CacheManager() { _simpleObjectCache = new ReferenceCache<MutableObject>(); _listObjectCache = new ReferenceCache<List<MutableObject>>(); _dictionaryCache = new ReferenceCache< Dictionary<string, MutableObject>>(); } public void RefreshDaily() { //Get latest versions of the List or Dictionary, //from some report repository, //and do a put. The put is a reference swap. _simpleObjectCache.Put(GetLatestVersion()); _listObjectCache.Put(GetLatestVersionList()); _dictionaryCache.Put(GetLatestVersionMap()); } public MutableObject SimpleObject { get { return _simpleObjectCache.Get(); } } public List<MutableObject> ListObject { get { return _listObjectCache.Get(); } } public Dictionary<String, MutableObject> Map { get { return _dictionaryCache.Get(); } } ... }
Granted, the example is a bit contrived and one could argue I should be using MemoryCache or insert_your_favourite_cache_here, but this isn't entirely uncommon, so bear with me on this one. The ReferenceCache class is in no way thread safe. Here's how I would change it to make it thread safe:
/// <summary> /// Represents a simple cache that provides ready access to /// an instance of a reference type. /// The access and modification of reference are threadsafe. /// However, it gives no guarantees about the threadsafety /// of a reference type. /// </summary> /// <typeparam name="T">Any reference type</typeparam> public class ReferenceCache<T>: IReferenceCache<T> where T: class { private T _objRef; public T Put(T t) { //All we're doing here is swapping the //reference in a thread-safe manner. //This does not mean that all mutations //on the actual object is threadsafe. //More on that later... return Interlocked.Exchange(ref _objRef, t); } public T Get() { //If the accessor holds on to a //reference received by this method, //that will not get updated when a put //is performed on this cache. return Interlocked.CompareExchange( ref _objRef, default(T), default(T)); } }
Interlocked methods are easy to use, are faster than many other synchronization mechanisms and more importantly, lends itself to be used in such scenarios. Admittedly, there are scenarios where the lock keyword is a better fit, particularly from the perspective of making your code readable. However, as you can see in this scenario, Interlocked is less intrusive and provides all the benefits listed earlier.
The ubiquitous lock should not be so prevalent anymore.







