Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it more efficient to lock or try-catch dictionary in a thread (.NET)

I have a generic Dictionary that I am using as a cache in a threaded .NET project (C#). I will be doing a lot of reads on the dictionary (potentially hundreds or more per second at peak times).

I have a simple Get(int id) method that should return a CustomObject if it's in the cache, or null otherwise.

My question: Is it faster/more efficient to lock the dictionary, or just use a try/catch block?

Assuming the dictionary is stored in a variable named "dic".

Lock Sample:

public CustomObject Get(int id)
{
   lock(dic)
   {
      if (dic.ContainsKey(id))
         return dic[id];
   }
   return null;
}

Try/Catch Sample:

public CustomObject Get(int id)
{
   try
   {
      return dic[id];
   }
   catch
   {
      return null;
   }
}
like image 802
Rick Avatar asked Aug 04 '11 00:08

Rick


People also ask

Do I need to lock ConcurrentDictionary?

ConcurrentDictionary<TKey,TValue> is designed for multithreaded scenarios. You do not have to use locks in your code to add or remove items from the collection. However, it is always possible for one thread to retrieve a value, and another thread to immediately update the collection by giving the same key a new value.

Is lock thread safe C#?

No, it's not thread safe. Add and Count may be executed at the "same" time. You have two different lock objects.

When should I use lock C#?

C# lock in thread The lock keyword is used to get a lock for a single thread. A lock prevents several threads from accessing a resource simultaneously. Typically, you want threads to run concurrently. Using the lock in C#, we can prevent one thread from changing our code while another does so.

Why should you avoid the lock keyword?

Avoid using 'lock keyword' on string object String object: Avoid using lock statements on string objects, because the interned strings are essentially global in nature and may be blocked by other threads without your knowledge, which can cause a deadlock.


2 Answers

I think you should test it in your own environment. Basically:

  • Lock is cheap
  • Try without getting an exception is cheap, maybe even cheaper then lock
  • Try and getting exception is very expensive

So now the question is, how often you expect to have cache-miss, and therefore get an exception thrown. I would go for lock() as it's execution time is not dependent on whether you will or not get cache-hit, which means it's more predictable and measurable, while still - very cheap. I don't think that hundreds hits per second would be any problem.

Simple tests I've made indicate, that getting cache-miss with try/catch is very, very expensive.

Edit:

Simple test shows that:

  • try-no throw costs about 2ms for 100k retrieves
  • lock costs about 6ms
  • try-throw costs about 4seconds

Which means, got for lock(), because it's more efficient then try/catch if you're getting more then 1 cache miss per few thousands tries, and it's much more stable, being not depended on luck.

like image 161
Marcin Deptuła Avatar answered Oct 28 '22 15:10

Marcin Deptuła


You can go ahead and write off the try-catch option. I do not know if it is slower or not, but I do know that it will not always yield correct, consistent, and predictable results if there is another thread updating the Dictionary. The problem is that at some point the writer will have the Dictionary in a half-baked state and there is no telling what the readers will see. This just will not work.

Option 1: If .NET 4.0 is available to you then I would use ConcurrentDictionary.

Option 2: If you are using .NET 3.5 then you can download the Reactive Extensions backport. ConcurrentDictionary is included in System.Threading.dll.

Option 3: Another idea is to keep two separate copies of the Dictionary. One would only be used for reading while the other would serve as the official copy that accepts updates. Anytime you update the "official" Dictionary you would clone it and overwrite the reference of the copy.

public class Example
{
  // This is the official version which can accept updates.
  private readonly Dictionary<int, CustomObject> official = new Dictionary<int, CustomObject>();

  // This is a readonly copy. This must be marked as volatile for this to work correctly.
  private volatile Dictionary<int, CustomObject> copy = new Dictionary<int, CustomObject>();

  public class Example()
  {
  }

  public void Set(int id, CustomObject value)
  {
    lock (official)
    {
      // Update the official dictionary.
      official[id] = value;

      // Now create a clone of the official dictionary.
      var clone = new Dictionary<int, CustomObject>();
      foreach (var kvp in official)
      {
        clone.Add(kvp.Key, kvp.Value);
      }

      // Swap out the reference. 
      copy = clone;
    }
  }

  public CustomObject Get(int id)
  {   
    // No lock is required here.
    CustomObject value = null;
    if (copy.TryGetValue(id, out value))
    {
      return value;
    }
    return null;
  }

}

This option does not work well if there are a lot of items in the Dictionary or if updates to the official copy happen frequently. But, it is a trick I do use from time to time.

Option 4: An equally reasonable approach would be to stick with the plain old lock.

like image 6
Brian Gideon Avatar answered Oct 28 '22 13:10

Brian Gideon