Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read elimination and concurrency

Tags:

c#

Given the following simple code:

class Program
{
    static bool finish = false;

    static void Main(string[] args)
    {
        new Thread(ThreadProc).Start();
        int x = 0;
        while (!finish)
        {
            x++;
        }
    }

    static void ThreadProc()
    {
        Thread.Sleep(1000);
        finish = true;
    }
}

and running it in the Release mode with MSVS2015(.NET 4.6) we will get a never ending application. That happens because the JIT-compiler generates code which reads finish only once, hence ignoring any future updates.

The question is: why is the JIT-compiler allowed to do such an optimization? What part of the specification allows it?

like image 929
ixSci Avatar asked Jun 15 '16 18:06

ixSci


2 Answers

This is covered in section 10.5.3 - Volatile Fields in the C# Specification:

(I've emphasized the part which covers your observations below)

10.5.3 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 multithreaded programs that access fields without synchronization, such as that provided by the lock-statement (§8.12). These optimizations can be performed by the compiler, by the runtime 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.
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.

like image 144
Lasse V. Karlsen Avatar answered Nov 14 '22 21:11

Lasse V. Karlsen


The compiler thinks (makes the following promises) as follows:

I will execute your code in the order that you've asked me to, that is any instructions that run in your single-threaded code will be performed in the order they were written and based on this I will do any optimizations I please, that conform with single-threaded sequentiality.

Well, the compiler sees the finish variable not marked as volatile and thus he considers that it will not be changed by other threads, so he optimizes that away into considering the condition as being always true. On Debug mode, it has a more lax thinking and does not perform this optimization away.

More on this here.

like image 44
Tamas Ionut Avatar answered Nov 14 '22 21:11

Tamas Ionut