Evyatar
Evyatar

Reputation: 1157

Debug vs Release C# code

I have the following code:

 public static void Main(string[] args)
    {
        bool isComplete = false;

        var t = new Thread(() =>
        {
            int i = 0;
            while (!isComplete) i += 0;
        });

        t.Start();

        Thread.Sleep(500);
        isComplete = true;
        t.Join();
        Console.WriteLine("complete!");

    }

The program will hung in release mode and will give an output in debug mode ("complete!").

What is the reason for that?

Thanks.

Upvotes: 2

Views: 273

Answers (1)

odyss-jii
odyss-jii

Reputation: 2719

The value of the variable isComplete is most likely being loaded into a register in your release build to avoid the round-trip to read value from memory in the while loop.

Therefore, the loop will not "detect" when the value of isComplete is changed to true.

You need to indicate to the compiler that this variable is volatile: essentially telling the system to not make assumptions based on code executing in the current thread whether or not this memory changes (i.e., some other thread or process might change it).

See this SA answer for some details:

How do I Understand Read Memory Barriers and Volatile - StackOverflow

If you want to dig even deeper into memory and concurrency, then here is an excellent article by Fabian Giesen about cache coherency in multi-core systems:

Cache coherency primer

Atomic operations and contention

Here are good reasons why you should just use a lock unless you know what you are doing:

Volatile keyword usage vs Lock - StackOverflow

Here is MSDN doc on Volatile.Read method:

Volatile.Read Method()

Note that without some more detailed explanation (see above SA thread, or google) of the terms, the description in the MSDN documentation can be hard to translate into what is actually going on.

Here is the MSDN doc on volatile keyword:

volatile (C# Reference)

In C# this keyword will use a half-fence (as far as I know); you will also find this keyword in C for example, but there it only affects compiler optimizations (does not insert a memory barrier) as it was originally intended for reading memory-mapped I/O.

Example:

        bool isComplete = false;

        var t = new Thread(() =>
        {
            int i = 0;
            while (!Volatile.Read(ref isComplete)) i += 0;
        });

        t.Start();

        Thread.Sleep(500);
        isComplete = true;
        t.Join();
        Console.WriteLine("complete!");

Upvotes: 10

Related Questions