It was mentioned that i should post my answer from over here.
There is a technique called "Lock-free initialization" that does what you want:
interface
function getInstance: TObject;
implementation
var
AObject: TObject;
function getInstance: TObject;
var
newObject: TObject;
begin
if (AObject = nil) then
begin
//The object doesn't exist yet. Create one.
newObject := TObject.Create;
//It's possible another thread also created one.
//Only one of us will be able to set the AObject singleton variable
if InterlockedCompareExchangePointer(AObject, newObject, nil) <> nil then
begin
//The other beat us. Destroy our newly created object and use theirs.
newObject.Free;
end;
end;
Result := AObject;
end;
The use of InterlockedCompareExchangePointer erects a full memory barrier around the operation. It is possible one might be able to get away with InterlockedCompareExchangePointerAcquire or InterlockedCompareExchangeRelease to get away with an optimization by only having a memory fence before or after. The problem with that is:
- i'm not smart enough to know if Acquire or Release semantics will work
- you're constructing an object, the memory barrier performance hit is the least of your worries (it's the thread safety)
InterlockedCompareExchangePointer
Windows didn't add InterlockedCompareExchangePointer until sometime around 2003. In reality it is simply a wrapper around InterlockedCompareExchange
function InterlockedCompareExchangePointer(var Destination: Pointer; Exchange: Pointer; Comparand: Pointer): Pointer stdcall;
const
SPointerAlignmentError = 'Parameter to InterlockedCompareExchangePointer is not 32-bit aligned';
begin
{IFDEF Debug}
//On 64-bit systems, the pointer must be aligned to 64-bit boundaries.
//On 32-bit systems, the pointer must be aligned to 32-bit boundaries.
if ((NativeInt(Destination) mod 4) <> 0)
or ((NativeInt(Exchange) mod 4) <> 0)
or ((NativeInt(Comparand) mod 4) <> 0) then
begin
OutputDebugString(SPointerAlignmentError);
if IsDebuggerPresent then
Windows.DebugBreak;
end;
{ENDIF}
Result := Pointer(IntPtr(InterlockedCompareExchange(Integer(IntPtr(Destination)), IntPtr(Exchange), IntPtr(Comparand))));
end;
In XE6, i find InterlockedcompareExchangePointer implemented for 32-bit in Windows.Winapi implemented the same way (except for the safety checking):
{$IFDEF WIN32}
function InterlockedCompareExchangePointer(var Destination: Pointer; Exchange: Pointer; Comparand: Pointer): Pointer; inline;
begin
Result := Pointer(IntPtr(InterlockedCompareExchange(Integer(IntPtr(Destination)), IntPtr(Exchange), IntPtr(Comparand))));
end;
{$ENDIF}
In newer versions of Delphi you would, ideally, use the TInterlocked helper class from System.SyncObjs:
if TInterlocked.CompareExchange({var}AObject, newObject, nil) <> nil then
begin
//The other beat us. Destroy our newly created object and use theirs.
newObject.Free;
end;
Note: Any code released into public domain. No attribution required.