3

Is the following code safe (considering it in isolation) from a torn read?

private static double flopsErrorMargin = 0.01d;

public static double FlopsErrorMargin {
    get {
        double result = flopsErrorMargin;
        Thread.MemoryBarrier();
        return result;
    }
    set {
        Interlocked.Exchange(ref flopsErrorMargin, value);
    }
}

The atomic write operation (Interlocked.Exchange()) is required because a double is not guaranteed to be written in a single operation on the .NET platform (excepting implementation details on 64-bit environments).

However, do I also need a 'mirror' operation on the read side? E.g. am I still at risk of getting a torn read because I do not read the value atomically?

My hunch is that I will not, because I think that another memory access (e.g. a read) can not happen concurrently with any other atomic operation, even if that other access is not itself atomic. But I would like some confirmation!

Xenoprimate
  • 7,691
  • 15
  • 58
  • 95
  • 2
    Just going to throw it out there that, as with so many other very low level multithreading issues, your best bet is to just write code that doesn't have to deal with these types of questions. You should really be sure that taking the time to use a higher level multithreading framework with fairly conservative synchronization is just unacceptably slow before you even bother to *ask* a question like this. – Servy Dec 09 '14 at 19:40
  • In principle it is not safe, though in practice (at least for x86), I have never been able to reproduce a torn read. See [this answer](http://stackoverflow.com/questions/3679209/why-doesnt-this-code-demonstrate-the-non-atomicity-of-reads-writes/3679400#3679400) for some more information. I was surprised to observe this result. Of course, probably best not to rely on behavior that's not guaranteed. – Dan Bryant Dec 09 '14 at 19:40
  • @DanBryant Of course, it's worth noting that if there's something like a .00001% chance of failure in your code, there's pretty low odds of you actually finding out when it happens. A program would need to be very large and complex for the odds of failure to build up enough to be noticable is one in which diagnosing a bug like this would be *very* hard. – Servy Dec 09 '14 at 19:43
  • 1
    Note, though, that I agree with @Servy here; this is really the wrong place to be doing synchronization. You almost always want to be synchronizing at a higher level such that these concerns don't even come up. – Dan Bryant Dec 09 '14 at 19:43
  • @Servy, it was interesting, though, in that it is very easy to demonstrate by using an Int64 instead of a Double. That's what made me suspect that the CPU itself may be providing an atomicity guarantee when working with doubles that .NET doesn't attempt to guarantee. – Dan Bryant Dec 09 '14 at 19:44
  • @DanBryant Certainly possible, although writing code that would fail on another brand (or not-yet-designed model) of CPU sounds like a nightmare for a programmer; that is, unless you're writing code that you're only running on your own servers or something like that. x86 does have a reputation for being more conservative in its memory model than other manufacturers. – Servy Dec 09 '14 at 19:46
  • In response to the comments about why I'm synchronizing here at all: Well, I normally don't try to keep my synchronization as such a fine-grained level (of course it would be stupid to try!); but this is a single global variable that does need to be able to be changed at any point. Can't be helped. :) – Xenoprimate Dec 09 '14 at 20:55

2 Answers2

3

No, Torn reads are possible. Assume your field access is reading the data and interleaved partway by the Interlocked.Exchange, then other 32 bits will be the updated value of Exchange and thus produces torn read.

For atomic read, you need to use Interlocked.Read (in 32 bit machines).

The Read method is unnecessary on 64-bit systems, because 64-bit read operations are already atomic. On 32-bit systems, 64-bit read operations are not atomic unless performed using Read

Which also means that torn values are possible.

You could define your own atomic Read for double as follows

public static double Read(ref double location)
{
    return Interlocked.CompareExchange(ref location, 0d, 0d);
}

This is how Interlocked.Read(long) is implemented internally.

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
2

am I still at risk of getting a torn read because I do not read the value atomically?

Yes. The return value of Interlocked.Exchange won't be torn, and the value that flopsErrorMargin eventually ends up as will be value (those are the two guarantees that Interlocked.Exchange give you), but an unsynchronized read access could be torn.

Servy
  • 202,030
  • 26
  • 332
  • 449