4

I am confused with the documentation for .NET/C# regarding the volatile keyword vs System.Threading.Thread.VolatileRead/VolatileWrite and System.Threading.Volatile.Read/Write. I am trying to understand what exactly is guaranteed for a volatile field and what exactly these methods are doing.

I thought volatile provides the release/acquire semantics, but the documentation for Thread.VolatileRead/VolatileWrite makes me wonder if my understanding is actually correct.

This is the language reference for volatile: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/volatile

Adding the volatile modifier ensures that all threads will observe volatile writes performed by any other thread in the order in which they were performed. There is no guarantee of a single total ordering of volatile writes as seen from all threads of execution.

So far makes sense. This is the language specification: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#volatile-fields

For volatile fields, such reordering optimizations are restricted:

A read of a volatile field is called a volatile read. A volatile read has "acquire semantics"; that is, it is guaranteed to occur prior to any references to memory that occur after it in the instruction sequence. A write of a volatile field is called a volatile write. A volatile write has "release semantics"; that is, it is guaranteed to happen after any memory references prior to the write instruction in the instruction sequence.

These restrictions ensure that all threads will observe volatile writes performed by any other thread in the order in which they were performed. A conforming implementation is not required to provide a single total ordering of volatile writes as seen from all threads of execution.

Again, this looks like volatile provides the release/acquire semantics.

But then I look at the documentation for Thread.VolatileRead: https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.volatileread?view=netframework-4.8#System_Threading_Thread_VolatileRead_System_Int64__

Reads the value of a field. The value is the latest written by any processor in a computer, regardless of the number of processors or the state of processor cache. ... On a multiprocessor system, VolatileRead obtains the very latest value written to a memory location by any processor. This might require flushing processor caches.

For Thread.VolatileWrite:

Writes a value to a field immediately, so that the value is visible to all processors in the computer.

This looks stricter then an individual store/load fence (release/acquire), specifically the part about flushing processor caches, i.e. more strict guarantee than just volatile. But then the same document says:

In C#, using the volatile modifier on a field guarantees that all access to that field uses VolatileRead or VolatileWrite

So my question is - what is guaranteed for a volatile field with respect to the store buffer - just release/acquire, or the stronger Thread.VolatileRead/Write guarantees? Or is my understanding about VolatileRead/Write wrong and these are the same as volatile?

  • [Threading in C# - Memory Barriers and Volatility](http://www.albahari.com/threading/part4.aspx#_Memory_Barriers_and_Volatility) -- [A reproducible example of volatile usage](https://stackoverflow.com/a/6164770/7444103) -- [What is the purpose of 'volatile' keyword in C#](https://stackoverflow.com/a/4103879/7444103) – Jimi Jan 14 '20 at 02:55
  • Being confused with the `volatile` keyword is absolutely normal. I have tried to understand this keyword for months, and I am still at a loss. My takeaway from repeatedly failing to completely understand this keyword is that I should avoid using it. – Theodor Zoulias Jan 14 '20 at 07:51

1 Answers1

0
  1. There is no difference between System.Threading.Thread.VolatileRead/VolatileWrite and System.Threading.Volatile.Read/Write - these are identical helper methods that mimic a full memory barrier before read or write (not necessary the MFENCE instruction). Here is the internal implementation:
public static void VolatileWrite(ref sbyte address, sbyte value)
{
  Thread.MemoryBarrier();
  address = value;
}
  1. A volatile variable uses(-ed) acquire/release semantics on the obsolete IA processor architecture (Itanium) with a weaker memory model.

  2. With the most popular x86 architecture, the volatile modifier avoids compiler optimizations and also may use instructions with lock prefix to guarantee consistency.

All in all, the compiler may use various tricks to comply with the C# memory model, which states:

  1. No reads or writes can move before a volatile read or after a volatile write
  2. All writes have the effect of volatile write
Serge Semenov
  • 9,232
  • 3
  • 23
  • 24
  • 2
    `mfence` isn't used for `volatile` variables actually, nor any other fence, just normal loads and stores ([example](https://sharplab.io/#v2:EYLgHgbALANALiAhgZwLYB8ACAmAjAWAChMBmAAhzIGEyBvIsxsgNwHsAbROAS3YFMy3AHZwyYANwMmpQSLIBZABQBKOlKZMA9JrIB3AE7c4fEGSGsyqAGZ8hAYz7qNYsgF4yUbJMLOtO/XyIACam5pY29o4+vhQA7GLeGgC+RElAA==)) – harold Jan 14 '20 at 04:52
  • you are right, did some research and confirm the statement – Serge Semenov Jan 16 '20 at 00:48