2

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.

Evyatar
  • 1,107
  • 2
  • 13
  • 36
  • "Why this unsafe multithreading code behaves erratically" is not really useful question... While it is entertaining coding puzzle there is zero practical value in this... – Alexei Levenkov Jan 20 '18 at 19:48
  • @AlexeiLevenkov I wouldn't be surprised to see such code in real life in a production environment and people wandering why it's wrong and not working like on their development machine and then deciding to run debug builds in production. All just because of some optimizations they aren't aware of. – Wouter Jan 20 '18 at 20:52

1 Answers1

10

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!");
Jimi
  • 29,621
  • 8
  • 43
  • 61
odyss-jii
  • 2,619
  • 15
  • 21
  • Side note: `volatile` is wrong solution to write correct multithreading code, but it is fine for this fake example. – Alexei Levenkov Jan 20 '18 at 19:45
  • Indeed `volatile` keyword is not a magical solution for solving concurrency problems. Actual problems require careful consideration of all possible order of executions, possible instruction re-ordering by compiler or CPU, and on certain obscure platforms even cache consolidation between cores. – odyss-jii Jan 20 '18 at 19:49
  • Local variables cannot be declared volatile. That would not compile. – Antonín Lejsek Jan 20 '18 at 19:51
  • He should not use the keyword, use a half-fence memory barrier: ex `Volatile.Read`. – odyss-jii Jan 20 '18 at 19:52
  • I think this would be a better answer if it actually explained what `volatile` does (other than "makes the program work"). – Bradley Uffner Jan 20 '18 at 19:56
  • There are already several answers on SA which describe memory fences in detail. I can refer to one of them, but would be pointless to add yet another in-depth explanation. – odyss-jii Jan 20 '18 at 19:58
  • A link would be sufficient, or even just a quick summary. – Bradley Uffner Jan 20 '18 at 19:59
  • maybe even just quote MSDN: "The volatile keyword indicates that a field might be modified by multiple threads that are executing at the same time. Fields that are declared volatile are not subject to compiler optimizations that assume access by a single thread. This ensures that the most up-to-date value is present in the field at all times." – Bradley Uffner Jan 20 '18 at 20:00
  • You might want to use `Volatile.Write(ref isComplete, true);` – Wouter Jan 20 '18 at 21:07
  • I don't believe that that is necessary in this example. `Volatile.Write` is to ensure that writes are not re-ordered; that all previous writes are flushed before the next write. You might write a bunch of data and then set one flag which indicates that data is ready to be read, by using `Volatile.Write` when setting the flag you ensure that writes are not re-ordered so that the flag is set before all the other data has been flushed. This way you ensure that if the flag is set, then you can safely read the other data. – odyss-jii Jan 20 '18 at 21:12