How can I make the hash table cache implementation thread safe when it is flushed?

I have a class used to cache access to a database resource. It looks something like this:

//gets registered as a singleton
class DataCacher<T>
{
    IDictionary<string, T> items = GetDataFromDb();

    //Get is called all the time from zillions of threads
    internal T Get(string key)
    {
         return items[key];
    }

    IDictionary<string, T> GetDataFromDb() { ...expensive slow SQL access... }

    //this gets called every 5 minutes
    internal void Reset()
    {
          items.Clear();
    }
}

      

I've simplified this code a bit, but the bottom line is that there is a potential concurrency issue, while the items are cleaned up, if Get is called, things can go wrong.

Now I can just lock the blocking blocks in Get and Reset, but I'm worried that Get locks will slow down the site's performance as Get is called by every request thread in the web application many times.

I can do something with double-checked locks, I think, but I suspect there is a cleaner way to do this using something smarter than locking {}. What to do?

edit: Sorry, I haven't done this before, but the items.Clear () implementation I'm using isn't actually a straight dictionary. It is a wrapper around the ResourceProvider that requires the dictionary implementation to call .ReleaseAllResources () on each of the elements as they are removed. This means that the caller does not want to work with the old version in progress. With this in mind, is the Interlocked.Exchange method correct?

+2


source to share


3 answers


I would start testing it with only lock

; locks are very cheap when uncontested. However - a simpler scheme is to rely on the atomic nature of referential updates:

public void Clear() {
    var tmp = GetDataFromDb(); // or new Dictionary<...> for an empty one
    items = tmp; // this is atomic; subsequent get/set will use this one
}

      

You can also make field items

a volatile

to make sure it is not stored anywhere in the register.



This issue still has a problem that anyone expecting a given key might be frustrated (through exception), but that is a separate issue.

A more detailed option may be ReaderWriterLockSlim

.

+6


source


One option is to completely replace the IDictionary instance instead of cleaning it up. You can do this in a thread-safe way using the Exchange method on Interlocked .



+2


source


See if the database will tell you what data has changed. you can use

  • Trigger to write changes to the history table
  • Query Notifications (SqlServer and Oracle have these, others should do as well)
  • Etc

So you don't need to reload all the data based on the timer.


Otherwise.

I would make a Clear method to create a new IDictionary by calling GetDataFromDB () and then, once the data has been loaded, set the "items" field to point to the new dictionary. (The garbage collector will clean up the old dictionary if no threads access it.)

I don't think you are wondering if some threads get "old" results when reloading data - (if you do, then you just have to block all threads on blocking - it hurts!)

If you need the entire thread to navigate to the new dictionary at the same time, you need to declare the "items" field volatile and use the Exchange method in the Interlocked class. However, it is unlikely that you need this in real life.

0


source







All Articles