A reproducible example of volatile usage
I am look for a reproducible example that can demonstrate how volatile keyword works. I'm looking for something that works "wrong" without variable(s) marked as volatile and works "correctly" with it.
I mean some example that will demonstrate that order of write/read operations during the execution is different from expected when variable is not marked as volatile and is not different when variable is not marked as volatile.
I thought that I got an example but then with help from others I realized that it just was a piece of wrong multithreading code. Why volatile and MemoryBarrier do not prevent operations reordering?
I've also found a link that demonstrates an effect of volatile on the optimizer but it is different from what I'm looking for. It demonstrates that requests to variable marked as volatile will not be optimized out.How to illustrate usage of volatile keyword in C#
Here is where I got so far. This code does not show any signs of read/write operation reordering. I'm looking for one that will show.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
namespace FlipFlop
{
class Program
{
//Declaring these variables
static byte a;
static byte b;
//Track a number of iteration that it took to detect operation reordering.
static long iterations = 0;
static object locker = new object();
//Indicates that operation reordering is not found yet.
static volatile bool continueTrying = true;
//Indicates that Check method should continue.
static volatile bool continueChecking = true;
static void Main(string[] args)
{
//Restarting test until able to catch reordering.
while (continueTrying)
{
iterations++;
a = 0;
b = 0;
var checker = new Task(Check);
var writter = new Task(Write);
lock (locker)
{
continueChecking = true;
checker.Start();
}
writter.Start();
checker.Wait();
writter.Wait();
}
Console.ReadKey();
}
static void Write()
{
//Writing is locked until Main will start Check() method.
lock (locker)
{
WriteInOneDirection();
WriteInOtherDirection();
//Stops spinning in the Check method.
continueChecking = false;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void WriteInOneDirection(){
a = 1;
b = 10;
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void WriteInOtherDirection()
{
b = 20;
a = 2;
}
static void Check()
{
//Spins until finds operation reordering or stopped by Write method.
while (continueChecking)
{
int tempA = a;
int tempB = b;
if (tempB == 10 && tempA == 2)
{
continueTrying = false;
Console.WriteLine("Caught when a = {0} and b = {1}", tempA, tempB);
Console.WriteLine("In " + iterations + " iterations.");
break;
}
}
}
}
}
Edit:
As I understand an optimization that causes reordering can come from JITer or from hardware itself. I can rephrase my question. Does JITer or x86 CPUs reorder read/write operations AND is there a way to demonstrate it in C# if they do?
The exact semantics of volatile is a jitter implementation detail. The compiler emits the Opcodes.Volatile IL instruction where ever you access a variable that's declared volatile. It does some checking to verify that the variable type is legal, you can't declare value types larger than 4 bytes volatile but that's where the buck stops.
The C# language specification defines the behavior of volatile, quoted here by Eric Lippert. The 'release' and 'acquire' semantics is something that only makes sense on a processor core with a weak memory model. Those kind of processors have not done well in the market, probably because they are such an enormous pain to program. The odds that your code will ever run on a Titanium are slim to none.
What's especially bad about the C# language specification definition is that it doesn't mention at all what really happens. Declaring a variable volatile prevents the jitter optimizer from optimizing the code to store the variable in a cpu register. Which is why the code that Marc linked is hanging. This will only happen with the current x86 jitter, another strong hint that volatile is really a jitter implementation detail.
The poor semantics of volatile has a rich history, it comes from the C language. Whose code generators have lots of trouble getting it right as well. Here's a interesting report about it (pdf). It dates from 2008, a good 30+ years of opportunity to get it right. Or wrong, this goes belly-up when the code optimizer is forgetting about a variable being volatile. Unoptimized code never has a problem with it. Notable is that the jitter in the 'open source' version of .NET (SSLI20) completely ignores the IL instruction. It can also be argued that the current behavior of the x86 jitter is a bug. I think it is, it is not easy to bump it into the failure mode. But nobody can argue that it actually is a bug.
The writing is on the wall, only ever declare a variable volatile if it is stored in a memory mapped register. The original intention of the keyword. The odds that you'll run into such a usage in the C# language should be vanishingly small, code like that belongs in a device driver. And above all, never assume that it is useful in a multi-threading scenario.
You can use this example to demonstrate the different behavior with and without volatile
. This example must be compiled using a Release build and ran outside of the debugger1. Experiment by adding and removing the volatile
keyword to the stop
flag.
What happens here is that the read of stop
in the while
loop is reordered so that it occurs before the loop if volatile
is omitted. This prevents the thread from ending even after the main thread set the stop
flag to true
.
class Program
{
static bool stop = false;
public static void Main(string[] args)
{
var t = new Thread(() =>
{
Console.WriteLine("thread begin");
bool toggle = false;
while (!stop)
{
toggle = !toggle;
}
Console.WriteLine("thread end");
});
t.Start();
Thread.Sleep(1000);
stop = true;
Console.WriteLine("stop = true");
Console.WriteLine("waiting...");
// The Join call should return almost immediately.
// With volatile it DOES.
// Without volatile it does NOT.
t.Join();
}
}
It should also be noted that subtle changes to this example can reduce its probability of reproducibility. For example, adding Thread.Sleep
(perhaps to simulate thread interleaving) will itself introduce a memory barrier and thus the similar semantics of the volatile
keyword. I suspect Console.WriteLine
introduces implicit memory barriers or otherwise prevents the jitter from using the instruction reordering operation. Just keep that in mind if you start messing with the example too much.
1I believe that framework version prior to 2.0 do not include this reordering optimization. That means you should be able to reproduce this behavior with version 2.0 and higher, but not the earlier versions.