I was thrilled to see the new System.Collections.Concurrent
namespace in .Net 4.0, quite nice! I've seen ConcurrentDictionary
, ConcurrentQueue
, ConcurrentStack
, ConcurrentBag
and BlockingCollection
.
One thing that seems to be mysteriously missing is a ConcurrentList<T>
. Do I have to write that myself (or get it off the web :) )?
Am I missing something obvious here?
I gave it a try a while back (also: on GitHub). My implementation had some problems, which I won't get into here. Let me tell you, more importantly, what I learned.
Firstly, there's no way you're going to get a full implementation of IList<T>
that is lockless and thread-safe. In particular, random insertions and removals are not going to work, unless you also forget about O(1) random access (i.e., unless you "cheat" and just use some sort of linked list and let the indexing suck).
What I thought might be worthwhile was a thread-safe, limited subset of IList<T>
: in particular, one that would allow an Add
and provide random read-only access by index (but no Insert
, RemoveAt
, etc., and also no random write access).
This was the goal of my ConcurrentList<T>
implementation. But when I tested its performance in multithreaded scenarios, I found that simply synchronizing adds to a List<T>
was faster. Basically, adding to a List<T>
is lightning fast already; the complexity of the computational steps involved is miniscule (increment an index and assign to an element in an array; that's really it). You would need a ton of concurrent writes to see any sort of lock contention on this; and even then, the average performance of each write would still beat out the more expensive albeit lockless implementation in ConcurrentList<T>
.
In the relatively rare event that the list's internal array needs to resize itself, you do pay a small cost. So ultimately I concluded that this was the one niche scenario where an add-only ConcurrentList<T>
collection type would make sense: when you want guaranteed low overhead of adding an element on every single call (so, as opposed to an amortized performance goal).
It's simply not nearly as useful a class as you would think.