8
class Program
{
    private static volatile int value;

    public static void Increment()
    {
        for (int i =0; i <100000; i++)
        {
            value++;
        }
    }

    public static void Decrement()
    {
        for (int j =0 ; j < 100000; j++)
        {
            value--;
        }
    }

    public static void ThreadTest()
    {
        value = 0;

        var incrementThread = new Thread(Increment);

        var decrementThread = new Thread(Decrement);

        incrementThread.Start();

        decrementThread.Start();

        incrementThread.Join();

        decrementThread.Join();

        Console.WriteLine("Value of value {0}", value);
    }

    static void Main(string[] args)
    {
        ThreadTest();
    }
}
user673979
  • 185
  • 1
  • 5
  • Do you mean it does in release mode? I tried this and got 0 in debug mode with debugger attached and not attached. I only got non-0 results when in release mode. Well, "release mode" being with optimizations turned on. – Adam Houldsworth Mar 23 '11 at 22:57

2 Answers2

17

Because it is not supposed to... ++ and -- are not atomic operations (unlike Interlocked.XXXX opreations - Interlocked.Increment).

If you write down each step of ++ and -- and see how both can be intermixed by different threads you'll see why:

increment

1: load value to temp
2: add temp, 1
3: store temp to value

decrement

4: load value to temp2
5: substruct temp2, 1
6: store temp2 to value

So if order is 1,2,3,4,5,6 you get value = 0; but if order is 1,2,4,5,6,3 you get value = 1.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • Why would it compile to this when there's an assembly instruction for just about every CPU on the planet that increments the value of the content of a memory address? I'm not saying you are wrong, I'm only asking. – Jonas Elfström Mar 23 '11 at 23:17
  • 1
    ++ will be eventually compiled into somthing like `inc [0x1234566]` machine code, but eventually CPU had to perform operations I've writen out. "Temp" variables could be either registers or different levels of CPU cache, the main point is that without explicit synchronization reading and writing to memory from different threads can be randomly intermixed. – Alexei Levenkov Mar 23 '11 at 23:23
  • 3
    Specifying volatile does not imply synchronisation. It tells the compiler to never use a cached (i.e. register) value when accessing the variable. – Richard Schneider Mar 24 '11 at 00:13
1

Only trying to make the things simpler... I fought this issue back in the day as well :D

Volatile ensure you read the latest value, and when you write all threads see that new value (and that is what volatile operations are for), but it doesn't ensure that between the read and the write, other thread is not going to modify the value. In the other hand, Interlocked (that provides atomic operations) does ensure it.

Volatile operations are good, when for example a thread or threads read and other modify. For example if you have a volatile Boolean _disposed flag in your class, so if one thread dispose it, it's marked as disposed for all threads straight away.

vtortola
  • 34,709
  • 29
  • 161
  • 263