How should "Double-Checked Locking" be implemented in Delphi?
Asked Answered
P

2

5

In C#, the following code (from this page) can be used to lazily instantiate a singleton class in a thread safe way:

  class Foo {
        private volatile Helper helper = null;
        public Helper getHelper() {
            if (helper == null) {
                lock(this) {
                    if (helper == null)
                        helper = new Helper();
                }
            }
            return helper;
        }
    }

What would be the equivalent thread safe Delphi code?


The article also mentions two problems with Double Checked Locking in Java:

  • it is possible that the new object is constructed before the helper reference is made to point at the newly created object meaning that two objects are created
  • it is possible that the helper reference is made to point at a block of memory while the object is still being created meaning that a reference to an incomplete object will be returned

So while the code of the C# and the Java version in the mentioned article look almost identical, only the C# version works as expected. Which leads to the additional question if these two problems also exist in a Delphi version of Double-Checked Locking?

Pomfret answered 17/12, 2010 at 21:56 Comment(0)
W
7

Use System.TMonitor to lock the object instance in a thread safe way.

function TFoo.GetHelper(): THelper;
begin
  if not Assigned(FHelper) then
  begin
    System.MonitorEnter(Self);
    try
      if not Assigned(FHelper) then
        FHelper := THelper.Create();
    finally
      System.MonitorExit(Self);
    end;
  end;
  Result := FHelper;
end;

For further reference look at Lock my object..., please! from Allen Bauer. In fact, the rep. I gather from this should go to Allen.

Whirlybird answered 17/12, 2010 at 22:24 Comment(6)
I could not have said it better myself ;-)Blemish
I have the feeling that something essential is missing in this code: the C# implementation uses the volatile keyword for the private Helper variable. I guess that FHelper must be declared as threadvar?Pomfret
threadvar would result in one per thread rather than a singleton shared by all threads. The volatile may be due to .net memory model. But the code above is correct on x86.Francefrancene
@mjn: I'm not a .NET expert, by the C# volatile reference it's clear that threadvar is not needed here, as said by @David Heffernan. IMHO using Delphi there is no need for such keyword because the "most up to date value" is present in the field all the times, obviously protecting the multithread memory access with a Synchronization object, like monitor, critical section, TMultiReadExclusiveWriteSynchronizer or such.Whirlybird
@jachgate The volatile statement in C# is there to force the compiler to erect memory barriers to prevent the re-ordering that breaks double-checked locking - see my answer.Francefrancene
@David Heffernan: I tried several times, but the linked page just don't respond, maybe the access is blocked or we just are in very different time-zones and the host is working office hours. Anyway, I found it using google cache now and I understand what volatile means, thanks.Whirlybird
F
3

Of course, it's always worth remembering that Double-Checked Locking is Broken. This issue turns out not to apply to the x86 memory model but it's always worth bearing in mind for the future. I'm sure there will be Delphi version at some point that will run on a platform with a memory model that is afflicted by this issue.

Embarcadero have started using a lock-free version of this pattern with interlocked compare/exchange. For example:

class function TEncoding.GetUnicode: TEncoding;
var
  LEncoding: TEncoding;
begin
  if FUnicodeEncoding = nil then
  begin
    LEncoding := TUnicodeEncoding.Create;
    if InterlockedCompareExchangePointer(Pointer(FUnicodeEncoding), LEncoding, nil) <> nil then
      LEncoding.Free;
  end;
  Result := FUnicodeEncoding;
end;

I realise this isn't an answer to the question but it didn't really fit in a comment!

Francefrancene answered 18/12, 2010 at 11:6 Comment(5)
Is Double-Checked Locking always broken? The mentioned article says that the C# example above 'works as expected'.Pomfret
It depends on the memory model in use. It works on x86 but I'm not sure about x64. It's broken on Java which has its own memory model. It's very complex though.Francefrancene
x86 and x64 use the same 'strong' memory model, however Itanium is a little different according to igoro.com/archive/volatile-keyword-in-c-memory-model-explainedPomfret
i'm not sure about .net and I have a feeling the memory model changed a few versions backFrancefrancene
Rergarding Java: the article linked in your answer also says "As of JDK5, there is a new Java Memory Model and Thread specification." ... "With this change, the Double-Checked Locking idiom can be made to work by declaring the helper field to be volatile."Pomfret

© 2022 - 2024 — McMap. All rights reserved.