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.
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;
Even if the assignment is made after construction, you still have the same problem. If two threads hit SacrifialCustomer at nearly the same time, both can execute the test if (customer = nil)
before one of them enters the critical section.
One solution to that problem is double check locking (test again after entering the critical section). With Delphi this works on some platforms, but is not guaranteed to work on all platforms. Other solutions use static construction, which works in many languages (not sure about Delphi) because the static initialization only happens when the class is referenced, so it is in effect lazy, and static initializers are in inherently thread safe. Another is using a interlocked exchange which combines test and assignment into an atomic operation (for a Delphi example see the second answer here: How should "Double-Checked Locking" be implemented in Delphi?).
No, Delphi does not assign a value to the target variable before the constructor returns. Much of Delphi's library relies on that fact. (Objects' fields are initialized to nil; an unhandled exception in the object's constructor triggers its destructor, which is expected to call Free
on all object fields that the constructor was assigning. If those fields had non-nil values, then further exceptions would occur.)
I elect not to address the bonus question because it's unrelated to the main question and because it's a much bigger topic than is appropriate for an afterthought.
Another solution to solve your problem is to use customer
pointer as atomic lock variable which prevent multiple object creation.
More about you can read at Busy-Wait Initialization
Read also: On Optimistic and Pessimistic Initialization