views:

209

answers:

6

Possible Duplicate:
Are C# auto-implemented static properties thread-safe?

In the following example class

static class Shared
{
    public static string[] Values { get; set; }
}

many reader threads read the Values string array periodically, while from time to time a single writer will replace the whole array with a new value using the setter. Do I need to use a ReaderWriterLock or is this something C# will handle automatically?

Edit: In my case the only required "thread safety" is: Nothing bad should happen when the writer replaces the array while the reader is searching for a value. I don't care if the readers will use the new value immediately as long as they will use it in the future

+2  A: 

This is not thread safe. Yes you will need to use a lock.

James Gaunt
This answer would be a lot more useful (or falsifiable) if it contained some detail - like the dangers you foresee.
Jon Skeet
"Thread-safe" and "not thread-safe" are used too loosely. In this case, reads and writes are guaranteed to be atomic, but possibly not in-order. A lock preserves ordering, but ordering may not be important here.
tc.
+4  A: 

It depends on what you mean by thread-safe.

The read and replacement are guaranteed to be atomic, but it's not guaranteed that a read following a write will necessarily read the fresh value.

In response to your edit...

Nothing bad will happen with your existing code (for example, torn reads), but there is no guarantee that your readers will ever see the new value. For example, it's possible -- though perhaps unlikely -- that the old reference would be cached in a register forever.

LukeH
I updated my question with the required level of thread safety.
xsl
+6  A: 

Without any extra protection, there's no guarantee that a reading thread will ever see the new value. In practice, as long as the reading thread is doing anything significant, it will see the new value. In particular, you'll never see "half" an update, with a reference pointing to dead space.

If you make the field volatile, I believe even this danger is removed - but lock-free programming is generally hard to reason about. This means abandoning it being an automatically-implemented property, of course.

Jon Skeet
@LukeH: Possibly... my understanding is limited too. However, I believe that it "more or less" makes sure you always read the last value. (That's even the MSDN description of it.)
Jon Skeet
+1  A: 

I would lock. For multiple read, occasional write use a ReaderWriterLockSlim - much more efficient than ReaderWriteLock.

static class Shared
{
    private static ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
    private static string[] _values;

    public static string[] Values 
    {
        get
        {
            _rwLock.EnterReadLock();
            try
            {
                return _values;
            }
            finally
            {
                _rwLock.ExitReadLock();
            }
        }
        set
        {
            _rwLock.EnterWriteLock();
            try
            {
                _values = value;
            }
            finally
            {
                _rwLock.ExitWriteLock();
            }
        }
    }
}
Michael
A plain old `lock` would be faster still. The reason is that RW locks have a limited set of scenarios where they actually are faster in practice. This is not one of them. I just did a benchmark on my machine and a `lock` was about 5x faster.
Brian Gideon
I agree Brian, a lock is quicker, but if we were talking about a large number of threads accessing the data at once, then perhaps this may have some advantages.In human terms the difference is pretty insignificant though!
Michael
+6  A: 

This use is thread safe:

string[] localValues = Shared.Values;
for (int index = 0; index < localValues.length; index++)
    ProcessValues(localValues[index]);

This use is not thread safe, and can result in out-of-bounds exceptions:

for (int index = 0; index < Shared.Values.Length; index++)
    ProcessValues(Shared.Values[index]);

I would prefer to make thread safe calls more natural by doing something like this:

static class Shared   
{
    private static string[] values;   
    public static string[] GetValues() { return values; }
    public static void SetValues(string[] values) { Shared.values = values; }
}

Of course, the users can still put GetValues() in the loops, and it would be just as bad, but at least it's obviously bad.

Depending on the situation, an even better solution might be to hand out copies, so that the calling code can't mutate the array at all. This is what I would usually do, but it may not be appropriate in your situation.

static class Shared
{
    private static string[] values;
    public static string[] GetValues()
    {
        string[] currentValues = values;
        if (currentValues != null)
            return (string[])currentValues.Clone();
        else
            return null;
    }
    public static void SetValues(string[] values)
    {
        Shared.values = values;
    }
}
Jeffrey L Whitledge
To add to this answer: a lock is not required in this lazy read scenario because taking a reference to the shared array serves as an atomic operation. Reading a reference (pointer) from a shared variable is thread safe even if another thread is about to write a new value to the shared variable. This is a form of "generational" versioning - every time you take a reference to the shared data, it is essentially an immutable snapshot of the data, kept alive until all references to it are released.
dthorpe
Clarification: Reads of reference pointers are atomic at the hardware level on x86 and x64 when the memory address is dword aligned, which I'm pretty sure is guaranteed by the .NET allocator. Nonaligned reads may take multiple clock cycles to read each half and combine them, and that creates a window of opportunity for a write in another thread to change the value in memory before the read has read the 2nd half.
dthorpe
@dthorpe - The important thing is that the C# language definition guarantees that a reference to an object will be copied in an atomic fasion regardless of the platform on which it's running or how the mechanism is implemented. And that's a pretty useful thing for circumstances like this. :-)
Jeffrey L Whitledge
Yes, it's good that the assertions made by the language definition are actually backed up by hardware implementation. :P
dthorpe
Big fan of lockless thread coordination. Wish I could + your answer up more.
dthorpe
This code still doesn't provide any freshness guarantee. It's theoretically possible that a reading thread might never see any new versions of the array. (Almost certainly not an issue in most real-world situations though.)
LukeH
@LukeH - I wrote this answer after the OP clarified the contextual meaning of "thread safety", so I focused on the specific issue requested. Adding "volatile" to the backing variable declaration ought to do the trick for freshness. As it stands, I also don't like the way the setter can modify the array contents after the assignment, but addressing that would start wandering way off topic!
Jeffrey L Whitledge
+1  A: 

To go slightly off-topic, the Java 2 1.5 memory model adds two guarantees (see Java theory and practice: Fixing the Java Memory Model, Part 2):

  • volatile reads/writes are also memory barriers.
  • final fields appear completely initialized outside of the constructor.

The latter is an interesting case: you used to be able to do foo = new String(new char[]{'a','b','c'}); in one thread and foo = "123"; System.out.println(foo) in another thread and print the empty string, because there was no guarantee that the writes to foo's final fields will happen before the write to foo.

I'm not sure of the details of .NET array initialization; it might be the case that you need to use Thread.MemoryBarrier() (at the start of the getter and at the end of the setter) to ensure that readers only see completely initialized arrays.

tc.