Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to lock a thread-safe collection in .net ? ( & when not to lock ? )

Ok, I have read Thread safe collections in .NET and Why lock Thread safe collections?.

The former question being java centered, doesn't answer my question and the answer to later question tells that I don't need to lock the collection because they are supposed to thread-safe. (which is what I thought)


Now coming to my question, I lot of developers I see, (on github and in my organisation) have started using the new thread-safe collection. However, often they don'tremove the lock around read & write operations. I don't understand this. Isn't a thread-safe collection ... well, thread-safe completely ?


What could be the implications involved in not locking a thread-safe collection ?

EDIT: PS: here's my case,

I have a lot of classes, and some of them have an attribute on them. Very often I need to check if a given type has that attribute or not (using reflection of course). This could be expensive on performance. So decided to create a cache using a ConcurrentDictionary<string,bool>. string being the typeName and bool specifying if it has the attribute. At First, the cache is empty, the plan was to keep on adding to it as and when required. I came across GetOrAdd() method of ConcurrentDictionary. And my question is about the same, if I should call this method without locking ?

The remarks on MSDN says:

If you call GetOrAdd simultaneously on different threads, addValueFactory may be called multiple times, but its key/value pair might not be added to the dictionary for every call.

like image 679
Bilal Fazlani Avatar asked Feb 10 '23 16:02

Bilal Fazlani


2 Answers

You should not lock a thread safe collection, it exposes methods to update the collection that are already locked, use them as intended.

The thread safe collection may not match your needs for instance if you want to prevent modification while an enumerator is opened on the collection (the provided thread safe collections allow modifications). If that's the case you'd better use a regular collection and lock it everywhere. The internal locks of the thread safe collections aren't publicly available.

It's hard to answer about implication in not locking a thread-safe collection. You don't need to lock a thread-safe collection but you may have to lock your code that does multiple things. Hard to tell without seeing the code.


Yes the method is thread safe but it might call the AddValueFactory multiple times if you hit an Add for the same key at the same time. In the end only one of the values will be added, the others will be discarded. It might not be an issue... you'll have to check how often you may reach this situation but I think it's not common and you can live with the performance penalty in an edge case that may never occur.

You could also build your dictionnary in a static ctor or before you need it. This way, the dictionnary is filled once and you don't ever write to it. The dictionary is then read only and you don't need any lock neither a thread safe collection.

like image 117
Guillaume Avatar answered Feb 13 '23 05:02

Guillaume


A method of a class typically changes the object from state A to state B. However, another thread may also change the state of the object during the execution of that method, potentially leaving the object in an instable state.

For instance, a list may want to check if its underlying data buffer is large enough before adding a new item:

void Add(object item)
{
    int requiredSpace = Count + 1;
    if (buffer.Length < requiredSpace)
    {
        // increase underlying buffer
    }

    buffer[Count] = item;
}

Now if a list has buffer space for only one more item, and two threads attempt to add an item at the same time, they may both decide that no additional buffer space is required, potentially causing an IndexOutOfRangeException on one of these threads.

Thread-safe classes ensure that this does not happen.

This does not mean that using a thread-safe class makes your code thread-safe:

int count = myConcurrentCollection.Count;
myCurrentCollection.Add(item);
count++;
if (myConcurrentCollection.Count != count)
{
    // some other thread has added or removed an item
}

So although the collection is thread safe, you still need to consider thread-safety for your own code. The enumerator example Guillaume mentioned is a perfect example of where threading issues might occur.

In regards to your comment, the documentation for ConcurrentDictionary mentions:

All these operations are atomic and are thread-safe with regards to all other operations on the ConcurrentDictionary class. The only exceptions are the methods that accept a delegate, that is, AddOrUpdate and GetOrAdd. For modifications and write operations to the dictionary, ConcurrentDictionary uses fine-grained locking to ensure thread safety. (Read operations on the dictionary are performed in a lock-free manner.) However, delegates for these methods are called outside the locks to avoid the problems that can arise from executing unknown code under a lock. Therefore, the code executed by these delegates is not subject to the atomicity of the operation.

So yes these overloads (that take a delegate) are exceptions.

like image 39
C.Evenhuis Avatar answered Feb 13 '23 05:02

C.Evenhuis