Visibility of volatile writes in C#

139 Views Asked by At

According to section 14.5.4 of the C# language spec (ECMA 334, 6th Edition), volatile fields are all about preventing reordering:

14.5.4 Volatile fields

When a field_declaration includes a volatile modifier, the fields introduced by that declaration are volatile fields. For non-volatile fields, optimization techniques that reorder instructions can lead to unexpected and unpredictable results in multi-threaded programs that access fields without synchronization such as that provided by the lock_statement (§12.13). These optimizations can be performed by the compiler, by the run-time system, or by hardware. 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.

This is in contrast with the Java memory model, which also provides visibility guarantees (link):

A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable

In the same section, the C# spec also contains the snippet below which shows how a volatile write is used to ensure that the main thread correctly prints result = 143:

class Test
{
    public static int result;
    public static volatile bool finished;
    
    static void Thread2()
    {
        result = 143;
        finished = true;
    }

    static void Main()
    {
        finished = false;
        new Thread(new ThreadStart(Thread2)).Start();
        for (;;)
        {
            if (finished)
            {
                Console.WriteLine($"result = {result}");
                return;
            }
        }
    }
}

However, there's no mention of what guarantees the (eventual?) visibility of the write to finished. Is this just implicit, or is it covered elsewhere in the spec?

1

There are 1 best solutions below

11
Malt On

We need to make a distinction between writes and assignments.

Visibility of writes is a property of CPUs, not languages. Specifically, it's a question of Cache Coherence which has two requirements:

Write Propagation - Changes to the data in any cache must be propagated to other copies (of that cache line) in the peer caches.

Transaction Serialization - Reads/Writes to a single memory location must be seen by all processors in the same order.

If a CPU's caches are coherent (which they should be), writes will eventually be seen by all threads in some fixed order.

However, at the language level we are (loosely speaking) talking about assignments, not writes. So, on CPUs with coherent caches the question becomes whether C# performs a memory write on volatile assignments, or can they be optimized away by the compiler.

Although such optimizations aren't explicitly prohibited in ECMA 334, the spec does contains guarantees that would break if volatile assignments could be compiled into something other than a memory write:

From section 14.5.4 ("Volatile Fields"):

These restrictions ensure that all threads will observe volatile writes performed by any other thread in the order in which they were performed.

From section 7.10 ("Execution Order"):

Execution of a C# program proceeds such that the side effects of each executing thread are preserved at critical execution points... The critical execution points at which the order of these side effects shall be preserved are references to volatile fields...

As for Java, my educated guess is that the JLS authors felt the need to specify the visibility guarantee (which importantly doesn't mention when the write will be visible) because the spec is written against not actual hardware but instead, "The Java Virtual Machine" which is "an abstract computing machine" (quote from section 1.2). Therefore, this property of the JVM had to be mentioned.

Big thanks to Theodor Zoulias who shared an excellent GitHub issue about this exact question and to Peter Cordes who chimed on both on GitHub and here (see the comments as well as this StackOverflow answer).