Search code examples
c#.netmultithreadingthread-safetyvolatile

Volatile Violates its main job?


According to 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.

Please notice the last sentence:

This ensures that the most up-to-date value is present in the field at all times.

However, there's a problem with this keyword.

I've read that it can change order of instructions:

First instruction       Second instruction         Can they be swapped?
Read                         Read                         No
Read                         Write                        No
Write                       Write                         No 
Write                       Read                          Yes! <----

This means John sets a value to a volatile field, and later Paul wants to read the field, Paul is getting the old value!

What is going here ? Isn't that it's main job ?

I know there are other solutions, but my question is about the volatile keyword.

Should I (as a programmer) need to prevent using this keyword - because of such weird behavior ?


Solution

  • The MSDN documentation is wrong. That is most certainly not what volatile does. The C# specification tells you exactly what volatile does and getting a "fresh read" or a "committed write" is not one of them. The specification is correct. volatile only guarantees acquire-fences on reads and release-fences on writes. These are defined as below.

    • acquire-fence: A memory barrier in which other reads and writes are not allowed to move before the fence.
    • release-fence: A memory barrier in which other reads and writes are not allowed to move after the fence.

    I will try to explain the table using my arrow notation. A ↓ arrow will mark a volatile read and a ↑ arrow will mark a volatile write. No instruction can move through the arrowhead. Think of the arrowhead as pushing everything away.

    In the following analysis I will use to variables; x and y. I will also assume that they are marked as volatile.

    Case #1

    Notice how the placement of the arrow after the read of x prevents the read of y from moving up. Also notice that the volatility of y is irrelevant in this case.

    var localx = x;
    ↓
    var localy = y;
    ↓
    

    Case #2

    Notice how the placement of the arrow after the read of x prevents the write to y from moving up. Also notice that the volatility of either of x or y, but not both, could have been omitted in this case.

    var localx = x;
    ↓
    ↑
    y = 1;
    

    Case #3

    Notice how the placement of the arrow before the write to y prevents the write to x from moving down. Notice that the volatility of x is irrelevant in this case.

    ↑
    x = 1;
    ↑
    y = 2;
    

    Case #4

    Notice that there is no barrier between the write to x and the read of y. Because of this the either the write to x can float down or the read of y can float up. Either movement is valid. This is why the instructions in the write-read case can be swapped.

    ↑
    x = 1;
    var localy = y;
    ↓
    

    Notable Mentions

    It is also important to note that:

    • x86 hardware has volatile semantics on writes.
    • Microsoft's implementation of the CLI (and suspect Mono's as well) has volatile semantics on writes.
    • The ECMA specification does not have volatile semantics on writes.