3

Suppose I have singleton class that acts as a data cache. Multiple threads will read from the cache, and a single thread will periodically refresh it. It looks something like this:

public sealed class DataStore
{
    public static DataStore Instance { get { return _instance; } }
    public Dictionary<Foo, Bar> FooBar { get; private set; }

    static DataStore() { }
    private DataStore() { }

    public void Refresh() {
        FooBar = GetFooBarFromDB();
    }

    private static readonly DataStore _instance = new DataStore();
}

My question is essentially, is it safe to Refresh() while other threads may be accessing FooBar? Do I need to be using locks, or are my getting and setting operations atomic? Do I need to explicitly declare volatile fields to back up my properties?

P.S., If someone can think of a more descriptive title for this question, I would gladly welcome it.

Edit: Fixed my example to correct obviously non-atomic code.

Eric
  • 5,842
  • 7
  • 42
  • 71
  • (1) Sorry, but why do you have that static ctor? (2) You don't need that `Instance`. Simply make `_instance` public. (3) IMHO, no, this is *not* thread safe. – Andre Calil Jul 31 '12 at 00:54
  • @Andre Calil: `Instance` is read-only. Making the `_instance` public will change it to writeable – zerkms Jul 31 '12 at 00:55
  • @zerkms You could have something like `{ get; private set; }`, right? – Andre Calil Jul 31 '12 at 00:57
  • 2
    "Thread safe" is a vague term. Do you mean to ask "will this crash / throw an exception"? (Maybe, if two threads try to modify the contents of the same `FooBar` value.) Or do you mean "will this do what I indend it to do?" (Impossible to tell from this little information.) Thread safety isn't a matter of "using thread safe classes" or "putting locks around all shared data" (which would work but might be terrible for performance), it's a matter of having the correct (desired) behaviour in the face of concurrency. – millimoose Jul 31 '12 at 00:58
  • Personally I just wouldn't expose an entire `Dictionary` to other classes / threads, and instead let them work over either point-in-time snapshots; or if you don't need consistent snapshots, the enumerator of a `ConcurrentDictionary`. (Of course you then need to make sure clients can't or won't mutate the objects stored in the dictionary.) – millimoose Jul 31 '12 at 01:15
  • @Andre Calil: not right. You proposed to remove `Instance` property. `public _instance` is read-write. Right? – zerkms Jul 31 '12 at 02:24
  • @zerkms `public static DataStore Instance { get; private set; }`. It's not absolutely read-only, but won't be *writable* from outside the class – Andre Calil Jul 31 '12 at 02:27
  • @Andre Calil: "*(2) You don't need that Instance. Simply make _instance public*" --- who proposed that? – zerkms Jul 31 '12 at 02:28
  • @zerkms I'm giving another suggestion based on your feedback. I've already agreed that this first suggestion wasn't correct. – Andre Calil Jul 31 '12 at 02:30
  • @Andre Calil: I don't see where you've agreed. Other discussion makes no sense, I do know how properties work :-) – zerkms Jul 31 '12 at 02:31
  • @zerkms I'm just pointing that OP has duplicated code that could be simplified, give a break. Go read the SO blog, the last to posts should make you think about your conduct. – Andre Calil Jul 31 '12 at 02:35
  • @Andre Calil: your simplifications proposal changes semantics. So it's not a duplication. And you even agreed (?) that. PS: sorry if I sounded rude, it wasn't intentionally PPS: I still cannot get if you agree that your item (2) is wrong or not. – zerkms Jul 31 '12 at 02:40

4 Answers4

8

Yes, in cases like that you need explicit synchronization, because another thread could get FooBar and start reading it before you have finished writing.

If you do this, however,

public void Refresh() {
    var tmp = new Dictionary<Foo, Bar>();
    // Fill out FooBar from DB
    FooBar = tmp;
}

then you wouldn't need to add explicit synchronization, because the switch from one reference over to the other reference is atomic.

Of course there is an implicit assumption here that there is no writing outside the Refresh method.

EDIT: You also should switch from an auto-implemented property to a manually implemented property with a backing variable declared with volatile modifier.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Doesn't FooBar need to either be marked `volatile` or assigned with `Interlocked.Exchange()` to ensure that this is safe? – Sean U Jul 31 '12 at 01:07
  • @SeanU You are right, without `volatile` the old value may be read instead of the old one. I fixed the answer, thanks! – Sergey Kalinichenko Jul 31 '12 at 01:21
  • 3
    I believe this solution does not go far enough. You shouldn't expose an ordinary dictionary to multiple threads. Should only use `ConcurrentDictionary` or expose only read methods via its interface. – aqwert Jul 31 '12 at 01:34
  • 3
    @aqwert You are right, since the methods for writing are exposed through the dictionary interface, the solution is not generally safe. That's why I mentioned the implicit assumption of no other writers: without this assumption, this solution breaks. Another solution is to use [readonly wrapper from this answer](http://stackoverflow.com/a/1269311/335858). – Sergey Kalinichenko Jul 31 '12 at 01:44
  • @dasblinkenlight Oh, great point - I can't believe it didn't occur to me that my example had such an obviously non-atomic aspect. It's an oversimplification from my actual code, which does not have the non-atomicity that my example has. Very helpful. – Eric Jul 31 '12 at 03:07
  • use [ReadOnlyDictionary](http://msdn.microsoft.com/en-us/library/gg712875.aspx) available from .Net 4.5 – Michael Freidgeim May 05 '13 at 04:23
2

Your example is not thread-safe. Dictionary is not a thread-safe class and any thread could be reading while Refresh is being executed. You can either put a lock around or use one of the thread-safe classes like ConcurrentDictionary.

millimoose
  • 39,073
  • 9
  • 82
  • 134
Icarus
  • 63,293
  • 14
  • 100
  • 115
1

Well, we agree that your current code is not thread-safe. So, you must use synchronization features, because FooBar is your critical section.

If you let it be public, you are expecting that people outside the DataStore class will act accordingly. However, this is a poor design decision.

So, I would suggest you to wrap everything into your current class, with something like this: What's the best way of implementing a thread-safe Dictionary?

Community
  • 1
  • 1
Andre Calil
  • 7,652
  • 34
  • 41
1

Because you are exposing the dictionary publicly then you run into more issues with the code you have written around access to the methods on the dictionary itself. As @Icarus has pointed out you should use ConcurrentDictionary but I would argue that any form of locking around the instance will not help you.

You can easily get one thread adding to the collection while another is iterating over it.

EDIT What I am saying.. never expose a static Dictionary or any other collection type. Always use the concurrent version

aqwert
  • 10,559
  • 2
  • 41
  • 61
  • "You can easily get one thread adding to the collection while another is iterating over it." – `ConcurrentDictionary` supports that scenario gracefully: http://msdn.microsoft.com/en-us/library/dd287131.aspx (One could argue that might very well be the point of concurrent collections.) – millimoose Jul 31 '12 at 01:07
  • Yes. that is why you should use `ConcurrentDictionary` and not an ordinary one. Simply putting a lock around the Refreash with an ordinary dictionary will not work like other people have suggested – aqwert Jul 31 '12 at 01:27
  • @aqwert, that is a very valid point. Based on answers I got to this question: http://stackoverflow.com/q/11639365/546561, I basically decided to give up on immutability in C#, and resolved to only protect my properties by convention. Perhaps I will take it up after all, though. – Eric Jul 31 '12 at 03:46
  • We found, that changing ConcurrentDictionary to [ReadOnlyDictionary](http://msdn.microsoft.com/en-us/library/gg712875.aspx) improved over-whole performance. – Michael Freidgeim May 06 '13 at 17:24