views:

413

answers:

5

I have a multi-threaded C# application. There is one dictionary that is accessed from N different threads at the same time. 99% of the calls are from thread A and the rest are from B, C, ...

Right now, I'm just locking the dictionary on each access. But it seems wasteful because 99% of the time that I'm calling lock, thread A already has the lock.

What would a more efficient way of doing this be?

Maybe some type of .Begin and .End calls could be required for threads B, C, ... and then Thread A would only have to check one bit on each call to see if any of the other threads are using the dictionary. Does anyone have a code sample of something like this implemented in a thread safe way?

+1  A: 

How do you perform the lock. For reading you don't need to lock out the other threads in the same "hard" way as if you are also updating. Either way you can be a bit more loose around the read operations. I would suggest looking into the ReaderWriterLockSlim (unless you are already using it):

class DictionaryHolder
{
    private IDictionary<int, string> _data = new Dictionary<int, string>();
    private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
    public void Update(int key, string value)
    {
        _lock.EnterWriteLock();            
        try
        {
            _data[key] = value;
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public string GetValue(int key)
    {
        _lock.EnterReadLock();
        try
        {
            if (_data.ContainsKey(key))
            {
                return _data[key];
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }
}

This will allow several threads to read from the dictionary "at the same time", while blocking access from other threads when updating it.

Fredrik Mörk
Thanks, I was thinking about the ReaderWriterLockSlim. Any idea how the perf of "_lock.EnterReadLock()" is compared to "Monitor.Enter"? I've never used it before.
Michael Covelli
I can't comment on the performance of acquiring the lock as such, but the upside with `_lock.EnterReadLock` is that is not locking out other threads that also want a reader lock. It's only around the update operation (protected by the `_lock.EnterReadLock`) that you have a lock that will block other threads from accessing the dictionary.
Fredrik Mörk
+2  A: 

You shouldn't worry about this unless a profiler tells you you are spending a lot of time in Monitor.Enter and company - in general, acquiring an uncontended lock or a lock you already hold is a very fast operation and should be similar in performance to the bit check that you propose. A lock operation is usually only slow when it has to block due to contention.

Michael
Thanks. Unfortunately, according to the ANTS profiler, the lock call is taking up a large percentage of the time that I spend in the function.
Michael Covelli
+2  A: 

You need to carefully examine your profiler data.

Both Monitor and RWLSlim won't actually "hard-lock" (as in drop down to the OS primitives) unless there's an actual contention; the will use Interlocked in all other cases, and perf implications of that are relatively minimal (aside from the cache flushes).

Performance wise, RWLockSlim is relatively expensive to create and a little more expensive than Monitor to enter. Its virtue is to allow muliple readers and a single writer.

If you are seeing hard locks showing up, then you have actual contention, in which case the 99%/1%/1%/1%/... ratio you stated probably doesn't reflect reality.

As previous posters noted, you should exmaine tha patten of use - in most cases you have occasional writes and massive reads, otherwise the system's consistency a somewhat difficult to enforfce. If that is the case, the RWlockSlim should remove unnecessary contention.

Bottomline : it all depends on what you are trying to do - as in what this dictionary is for and how is accessed. Sometimes it make sense to widen locks to prevent taking too many of those, and in some - very rare - cases you might want to play with lock-free-style primitives like Interlocked before you even try to hit the "real" lock.

Perhaps if you told us more about the scenario, we'd be able to help better.

Oleg Lvovitch
+1  A: 

ReaderWriterLockSlim does help when there are many readers and few writers. However, individual operation performance of ReaderWriterLockSlim seemed to be worse than that of Monitor according to the following link: A Performance Comparison of ReaderWriterLockSlim with ReaderWriterLock.

One option you may try is to play with Interlocked operations together with Event objects to synchronize threads. But, again, you should measure the performance of this approach as well.

Chansik Im
A: 

Try using System.Collections.Concurrent.ConcurrentDictionary in .NET 4.0

It's thread safe by design

Xaqron
That's a good option from .NET 4. Do you know how its implemented? I wonder how it performs vs. just locking all access to a regular Dictionary.
Michael Covelli
System.Collections.Concurrent members are built-in thread-safe. Nothing special to do. For example use Concurrent queue with multiple threads and call 'Enqueue' or 'Dequeue' without any considerations. If you wanna details, use Reflector on desired library for implementation. To my experience, the speed is good.
Xaqron