Subterranean IL: Volatile
Posted
by Simon Cooper
on Simple Talk
See other posts from Simple Talk
or by Simon Cooper
Published on Wed, 24 Nov 2010 13:22:00 GMT
Indexed on
2010/12/06
16:58 UTC
Read the original article
Hit count: 352
This time, we'll be having a look at the volatile.
prefix instruction, and one of the differences between volatile
in IL and C#.
The volatile.
prefix
volatile
is a tricky one, as there's varying levels of documentation on it. From what I can see, it has two effects:
- It prevents caching of the load or store value; rather than reading or writing to a cached version of the memory location (say, the processor register or cache), it forces the value to be loaded or stored at the 'actual' memory location, so it is then immediately visible to other threads.
- It forces a memory barrier at the prefixed instruction. This ensures instructions don't get re-ordered around the
volatile
instruction. This is slightly more complicated than it first seems, and only seems to matter on certain architectures. For more details, Joe Duffy has a blog post going into the details.
volatile
.
Caching field accesses
To demonstrate this, I created a simple multithreaded IL program. It boils down to the following code:
.class public Holder {If you compile and run this code, you'll find that the call to
.field public static class Holder holder
.field public bool stop
.method public static specialname void .cctor() {
newobj instance void Holder::.ctor()
stsfld class Holder Holder::holder
ret
}
}
.method private static void Main() {
.entrypoint
// Thread t = new Thread(new ThreadStart(DoWork))
// t.Start()
// Thread.Sleep(2000)
// Console.WriteLine("Stopping thread...")
ldsfld class Holder Holder::holder
ldc.i4.1
stfld bool Holder::stop
call instance void [mscorlib]System.Threading.Thread::Join()
ret
}
.method private static void DoWork() {
ldsfld class Holder Holder::holder
// while (!Holder.holder.stop) {}
DoWork:
dup
ldfld bool Holder::stop
brfalse DoWork
pop
ret
}
Thread.Join()
never returns - the DoWork
spinlock is reading a cached version of Holder.stop
, which is never being updated with the new value set by the Main
method. Adding volatile
to the ldfld
fixes this:
dup
volatile.
ldfld bool Holder::stop
brfalse DoWork
The volatile ldfld
forces the field access to read direct from heap memory, which is then updated by the main thread, rather than using a cached copy.
volatile
in C#
This highlights one of the differences between IL and C#. In IL, volatile
only applies to the prefixed instruction, whereas in C#, volatile
is specified on a field to indicate that all accesses to that field should be volatile
(interestingly, there's no mention of the 'no caching' aspect of volatile
in the C# spec; it only focuses on the memory barrier aspect). Furthermore, this information needs to be stored within the assembly somehow, as such a field might be accessed directly from outside the assembly, but there's no concept of a 'volatile field' in IL! How this information is stored with the field will be the subject of my next post.
© Simple Talk or respective owner