Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Delphi assign the variable before the object is constructed?

Does Delphi assign an instance variable before the object is fully constructed?

In other words, given a variable:

var
   customer: TCustomer = nil; 

we then construct a customer and assign it to the variable:

customer := TCustomer.Create;

Is it possible that customer can be not nil, but not point to a fully constructed TCustomer?


This becomes a problem when performing lazy initialization:

function SacrifialCustomer: TCustomer;
begin
   if (customer = nil) then
   begin
      criticalSection.Enter;
      try
         customer := TCustomer.Create;
      finally 
         criticalSection.Leave;
      end;
   end;
   Result := customer;
end;

The bug is in the line:

if (customer = nil) 

It is possible that another thread calls:

customer := TCustomer.Create;

and the variable is assigned a value before construction happens. This causes the thread to assume that customer is a valid object simply because the variable is assigned.

Can this multi-threaded singleton bug happen in Delphi (5)?


Bonus Question

Is there an accepted, thread-safe, one-time initialization design pattern for Delphi? Many people have implemented singletons in Delphi by overriding NewInstance and FreeInstance; their implementations will fail in multiple threads.

Strictly speaking i'm not after an answer on how to implement and singleton, but lazy-initialization. While singletons can use lazy-initialization, lazy initialization is not limited to singletons.

Update

Two people suggested an answer that contains a common mistake. The broken double-checked locking algorithm translated to Delphi:

// Broken multithreaded version
// "Double-Checked Locking" idiom
if (customer = nil) then
begin
   criticalSection.Enter;
   try
      if (customer = nil) then
         customer := TCustomer.Create;
   finally
      criticalSection.Leave;
   end;
end;
Result := customer;

From Wikipedia:

Intuitively, this algorithm seems like an efficient solution to the problem. However, this technique has many subtle problems and should usually be avoided.


Another buggy suggestion:

function SacrificialCustomer: TCustomer;
var
  tempCustomer: TCustomer;
begin
   tempCustomer = customer;
   if (tempCustomer = nil) then
   begin
      criticalSection.Enter;
      try
         if (customer = nil) then
         begin
            tempCustomer := TCustomer.Create;
            customer := tempCustomer;
         end;
      finally
         criticalSection.Leave;
      end;
   end;
   Result := customer;
end;

Update

i created some code and looked at the cpu window. It seems that this compiler, with my optimization settings, on this version of Windows, with this object, constructs the object first, then assigns the variable:

customer := TCustomer.Create;
       mov dl,$01
       mov eax,[$0059d704]
       call TCustomer.Create
       mov [customer],eax;
Result := customer;
       mov eax,[customer];

Of course i cannot say that's guaranteed to always work that way.

like image 805
Ian Boyd Avatar asked May 29 '12 19:05

Ian Boyd


1 Answers

My reading of your question is that you are asking this:

How can I, using Delphi 5 targeting x86 hardware, implement thread-safe lazy initialization of a singleton.

To the best of my knowledge you have three options.

1. Use a lock

function GetCustomer: TCustomer;
begin
  Lock.Acquire;
  try
    if not Assigned(Customer) then // Customer is a global variable
      Customer := TCustomer.Create;
    Result := Customer;
  finally 
    Lock.Release;
  end;
end;

The downside of this is that if there is contention on GetCustomer then the serialization of the lock will inhibit scaling. I suspect that people worry about that a lot more than is necessary. For example, if you have a thread that performs a lot of work, that thread can take a local copy of the reference to the singleton to reduce the contention.

procedure ThreadProc;
var
  MyCustomer: TCustomer;
begin
  MyCustomer := GetCustomer;
  // do lots of work with MyCustomer
end;

2. Double checked locking

This is a technique that allows you, once the singleton has been created, to avoid the lock contention.

function GetCustomer: TCustomer;
begin
  if Assigned(Customer) then
  begin
    Result := Customer;
    exit;
  end;

  Lock.Acquire;
  try
    if not Assigned(Customer) then
      Customer := TCustomer.Create;
    Result := Customer;
  finally 
    Lock.Release;
  end;
end;

Double checked locking is a technique with a rather chequered history. The most famous discussion is The "Double-Checked Locking is Broken" Declaration. This is set mostly in the context of Java and the problems described do not apply to your situation (Delphi compiler, x86 hardware). Indeed, for Java, with the advent of JDK5, we can now say that Double-Checked Locking is Fixed.

The Delphi compiler doesn't re-order the write to the singleton variable with respect to the construction of the object. What's more, the strong x86 memory model means that processor re-orderings don't break this. See Who ordered memory fences on an x86?

Simply put, double checked locking is not broken on Delphi x86. What's more, the x64 memory model is also strong and double checked locking is not broken there either.

3. Compare and swap

If you don't mind the possibility of creating multiple instances of the singleton class, and then discarding all but one, you can use compare and swap. Recent versions of the VCL make use of this technique. It looks like this:

function GetCustomer;
var
  LCustomer: TCustomer;
begin
  if not Assigned(Customer) then 
  begin
    LCustomer := TCustomer.Create;
    if InterlockedCompareExchangePointer(Pointer(Customer), LCustomer, nil) <> nil then
      LCustomer.Free;
  end;
  Result := Customer;
end;
like image 102
David Heffernan Avatar answered Oct 06 '22 21:10

David Heffernan