ThreadSafeStore
Developers and multithreading fall into 3 camps:
- Doesn’t know anything. This developer avoids thinking about other threads at all costs. Followers of the HttpContext. 90% of developers.
- Knows everything. This developer is either a savant or writes operating systems for fun. Most likely sports a sweet hippy beard. 3.14159% of developers.
- Doesn’t know everything but knows enough. This developer will happily write multithreaded applications. It turns out however he/she doesn’t know enough and said applications are full of hard to find, intermittent bugs just waiting to corrupt data and deadlock. The rest us.
I fall into the third category: knows enough to be dangerous.
The latest example of my thread safety failure is knowing that two threads modifying a dictionary at the same time is very bad, but then not considering that getting from a dictionary that is being modified is also not a terribly good idea. Thanks to Amir for the pointer.
ThreadSafeStore
I’ve written a simple helper class that wraps a Dictionary called ThreadSafeStore. ThreadSafeStore treats its internal Dictionary as immutable. Each time a new value is added the wrapper will create a new Dictionary with the new value and reassigns the internal reference. There is no chance a thread could access the Dictionary while it is being modified. ThreadSafeStore is aimed towards read performance with no lock on read required.
The downside to this approach is a new Dictionary is being created with every add. An improvement would be to introduce a second level Dictionary and buffer up new values before adding them to the main store, reducing total object’s allocated. For now I’ve left it simple. Suggestions welcome [:)]
public class ThreadSafeStore<TKey, TValue>
{
private Dictionary<TKey, TValue> _store;
private readonly Func<TKey, TValue> _creator;
public ThreadSafeStore(Func<TKey, TValue> creator)
{
if (creator == null)
throw new ArgumentNullException("creator");
_creator = creator;
}
public TValue Get(TKey key)
{
if (_store == null)
return AddValue(key);
TValue value;
if (!_store.TryGetValue(key, out value))
return AddValue(key);
return value;
}
private TValue AddValue(TKey key)
{
lock (this)
{
TValue value;
if (_store == null)
{
_store = new Dictionary<TKey, TValue>();
value = _creator(key);
_store[key] = value;
}
else
{
// double check locking
if (_store.TryGetValue(key, out value))
return value;
Dictionary<TKey, TValue> newStore = new Dictionary<TKey, TValue>(_store);
value = _creator(key);
newStore[key] = value;
_store = newStore;
}
return value;
}
}
}