Delphi for .NET and 'volatile'

published: Mon, 6-Feb-2006   |   updated: Sat, 6-Aug-2016

I had occasion over last weekend to do some research for an article on the .NET memory model. Since the article was for Delphi readers (that is, those using some version of Delphi for .NET) I needed to illustrate my point using Delphi code, and in particular the equivalent to the C# volatile keyword. There isn't one.

Unfortunately, it's true. I spent quite a bit of time in Google, both for the web and groups, and there just isn't any equivalent. (Note that my copy of Delphi 2005 refuses to show help any more, and after trying the couple of hacks I found on the web getting it to work and failing, I just gave up. I don't need no steenkin' help.)

I did start thinking that maybe Delphi called it something different (and hence I wasn't getting any hits because I didn't know the "magic" word) but a quick post on the borland.public.delphi.language.delphi.dotnet newsgroup confirmed my fears: there really is no equivalent.

That's a shame really because in some cases, especially for multithreaded code where you're trying to not use so many locks, volatile is required in several cases.

The other thing is that people don't really understand what volatile means in .NET. They assume it's the same thing as C++ calls fields that shouldn't reside in registers (a bit like the reverse of the register keyword). Nope. It has other semantics that requires some close reading and understanding of the .NET memory model.

Anyway, to get the effect of volatile in Delphi for .NET, you have to use the Thread.VolatileRead() and the Thread.VolatileWrite() static methods from the System.Threading namespace. This is not quite the same as the C# volatile keyword though: C# compiles that so that accesses to the field are prefixed with the IL volatile keyword, whereas calling the Thread class methods are emitted as, well, method calls.

Here's an example of their use when implementing the double-checked locking pattern for performance:

type
  HeavyDutySingleton = class(System.Object)
    private
      class var Instance : TObject;
      class var Lock : TObject;

      class function safeGetInstance : HeavyDutySingleton;
      class procedure safeSetInstance(value : HeavyDutySingleton);

      class constructor Create;
    public
      constructor Create;
      class function GetInstance : HeavyDutySingleton;
  end;

class constructor HeavyDutySingleton.Create;
begin
  Lock := TObject.Create;
end;

constructor HeavyDutySingleton.Create;
begin
  // heavy duty construction goes here
end;

class function HeavyDutySingleton.safeGetInstance : HeavyDutySingleton;
begin
  Result := Thread.VolatileRead(Instance) as HeavyDutySingleton;
end;

class procedure HeavyDutySingleton.safeSetInstance(value : HeavyDutySingleton);
begin
  Thread.VolatileWrite(Instance, value);
end;

class function HeavyDutySingleton.GetInstance : HeavyDutySingleton;
begin
  if (safeGetInstance = nil) then begin
    Monitor.Enter(Lock);
    try
      if (safeGetInstance = nil) then
        safeSetInstance(HeavyDutySingleton.Create);
    finally
      Monitor.Exit(Lock);
    end;
  end;
  Result := safeGetInstance;
end;

Note that it is totally up to the developer to ensure that all accesses to the volatile variable are through the two magic methods. If you miss one, you may have a very difficult to find multithreaded bug.

For fun, here's the exact same class written in C# (where the use of lock and volatile reduce the number of lines of code):

  class HeavyDutySingleton {
    private static object threadLock = new object();
    private static volatile HeavyDutySingleton instance;

    private HeavyDutySingleton() {
      // heavy duty construction goes here
    }

    public static HeavyDutySingleton GetInstance() {
      if (instance == null) {
        lock(threadLock) {
          if (instance == null) {
            instance = new HeavyDutySingleton();
          }
        }
      }
      return instance;
    }
  }

By the way, I recommend that you write all multithreaded code with locks. Write it first to work (and test on a multicore or multiprocessor or hyperthreaded machine) and then profile it. If you find that some of your code is suffering from lock convoys (that is, execution that seems to shunt from one thread to another, with threads going to sleep and waking up in sequence) then investigate some of these minimal locking or lock-free algorithms.

Writing multithreaded code is hard enough without throwing away locks.

Update: 7-Feb-2006

A couple of comments came in. The first was from my brother-in-law who develops in VB.NET: "For what it’s worth... there isn’t a volatile in VB.NET either. I was hoping it would be in .NET 2.0 but no luck." So one could say that Delphi is tracking VB.NET more than C#.

The second pointed out that the Delphi disassembler in Reflector didn't work properly with a volatile field, providing more proof that Delphi for .NET doesn't support volatile (or just proof for a bug in the disassembler <g>).