3

When you have a

long[] myArray = new long[256];

whose items are changed by multiple threads using

Interlocked.Increment(ref myArray[x])

it sure won't be possible to get a snapshot of myArray at one point in time, as there are non-locked writes going on concurrently, so I'm not trying to get that.

So do I really have to Volatile.Read of every single element like this to get a copy of all values at some point in the past?

long[] copy = new long[256];
for (int i = 0; i < 256; i++)
    copy[i] = Volatile.Read(ref myArray[i]);

As I'm not interested in a snapshot at one point in time, stale values are not a problem, but as 64-bit non-volatile reads are not atomic, I fear that the following may give me a pre-increment half of a long, and a post-increment half, which may give a value that never existed in the array.

long[] copy = new long[256];
for (int i = 0; i < 256; i++)
    copy[i] = myArray[i];

So is the Volatile.Read variant the proper choice, given I don't want to use any locking?

Evgeniy Berezovsky
  • 18,571
  • 13
  • 82
  • 156

2 Answers2

1

There are no such thing as an atomic type in C# (as you probably know), there are only atomic operations.

The Jitter and/or Processor can decide to reorder instructions, so you are correct in assuming you either need to

  • Serialize access with lock
  • Use the Interlocked class for writes (and in some cases reads)
  • Declare the variable volatile (though its not available on a 64 bit types, and doesn't apply to an array)
  • Or in your situation use Volatile.Read if you don't mind stale values.

To answer your question, and without seeing your code or how you are threading this, your approach seems like the right solution

Volatile.Read Method

Reads the value of a field. On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method

TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • Your third suggestion (declare volatile) doesn't apply to arrays - whether the items are 64bit or not. Because it would only cover the array reference, not the array content. – Evgeniy Berezovsky Oct 23 '18 at 05:18
1

If stale values is not an issue for you and all you need is atomic read (not ordered one) then on x64 you can just use plain read instead of Volatile.Read. It can be beneficial on on ARM systems where volatile reads/writes are fairly heavyweight as they are implemented with DMB.

Important According to this and that, you need to (build and) run your .Net program in 64-bit mode for this to work:

if you are running C# code on a 64 bit operating system in a 64 bit version of the CLR then reads and writes of 64 bit doubles and long integers are also guaranteed to be atomic

Evgeniy Berezovsky
  • 18,571
  • 13
  • 82
  • 156
OmariO
  • 506
  • 4
  • 11
  • That's unexpected, but very welcome news. Do you have any authoritative reference for that? Does this even hold for 32-bit builds on x64 machines? I think this deserves it's [own question](https://stackoverflow.com/questions/52959183/) – Evgeniy Berezovsky Oct 23 '18 at 23:41
  • @EugeneBeresovsky: Beware that in this approach, "stale values" might mean _values that never update at all_ after the first read (if you've got one reader thread that repeatedly reads the array). When you're not using any kind of synchronization, the compiler is allowed to assume that the current thread is the only one that accesses the array, and since the current thread doesn't write to the array, the compiler may assume that the second time the thread reads the array, the values haven't changed - so you might keep seeing the same snapshot forever. – Aasmund Eldhuset Oct 24 '18 at 02:56
  • @AasmundEldhuset That would be impracticably stale... I don't believe though that there is any .net (java/c/c++) compiler out there that analyzes on which thread modifications are performed - which isn't even possible statically for the general case, and for a runtime the overhead for tracking that would probably not worth it in 99% of cases. Do you have any reference for your statement? Perhaps even a real-life example? – Evgeniy Berezovsky Oct 24 '18 at 03:34
  • @EugeneBeresovsky: True; the compiler isn't actually trying to determine which thread does what - but as far as I understand, it is allowed to compile any synchronization-free code under the assumption that there will be only one thread executing it. I don't have a C# reference handy, but the Java Language Specification mentions how the compiler may choose to [reuse a value if it doesn't see any writes to it](https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4-C). – Aasmund Eldhuset Oct 24 '18 at 17:57
  • 1
    @EugeneBeresovsky: For C#, see the third code block in https://msdn.microsoft.com/en-us/magazine/jj883956.aspx for an example of a thread that never ends up seeing the value that is written by another thread. Basically, if you don't synchronize properly, there are no guarantees about data freshness. Depending on your compiler and even on each individual execution of your program (because many optimizations are performed by the JIT compiler), you may or may not run into problems, but if you don't synchronize properly, your program _does_ contain a bug waiting to happen. – Aasmund Eldhuset Oct 24 '18 at 18:00
  • Here's another example, this time from the [Golang documentation](https://golang.org/ref/mem): the text after the third example under "Incorrect synchronization" corroborates my statement that a thread might _never_ see the value written by another thread: "Worse, there is no guarantee that the write to `done` will ever be observed by `main`, since there are no synchronization events between the two threads." As mentioned, my understanding is that most languages specify that without explicit synchronization, code may be compiled under the assumption that there is only one thread. – Aasmund Eldhuset Jan 14 '20 at 03:18