Aquire Singleton class Instance Multithread
Asked Answered
J

2

2

To get the instance of the class with Singleton pattern, I want use the following function:

This is a sketch

interface

uses SyncObjs;

type
  TMCriticalSection = class(TCriticalSection)
  private
    Dummy : array [0..95] of Byte;
  end;

var
  InstanceNumber : Integer;
  AObject: TObject;
  CriticalSection: TMCriticalSection;

function getInstance: TObject;

implementation

uses Windows;

function getInstance: TObject;
begin
   //I Want somehow use InterlockedCompareExchange instead of CriticalSession, for example

   if InterlockedCompareExchange(InstanceNumber, 1, 0) > 0 then
   begin
     Result := AObject;
   end
   else
   begin
      CriticalSection.Enter;
      try
        AObject := TObject.Create;
      finally
        CriticalSection.Leave;
      end;
      InterlockedIncrement(InstanceNumber);
      Result := AObject
   end;
end;

initialization
  CriticalSection := TMCriticalSection.Create;
  InstanceNumber := 0;

finalization;
  CriticalSection.Free;

end.

Three Questions:

1- Is this design Thread Safe? Especially the with InterlockedExchange Part.
2- How to use the InterlockedCompareExchange? Is it possible to do what i'm trying?
3- Is this design better than involve all code within the critical section scope?

Remark: My object is thread safe, only the construction i need to serialize!
This is not the intire code, only the important part, i mean, the getInstance function.

Edit

I NEED to use some kind of singleton object.
Is there any way to use InterlockedCompareExchange to compare if the value of InstanceNumber is zero?
And
1 - Create the object only when is 0, otherwise, return the instance.
2 - When the value is 0: Enter in the critical section. Create the object. Leave critical section.
3 - Would be better to do this way, instead of involve all code within the critical section scope?

Jessamyn answered 17/5, 2013 at 11:48 Comment(0)
Z
4

Your design does not work and this can be seen even without any understanding of what InterlockedCompareExchange does. In fact, irrespective of the meaning of InterlockedCompareExchange, your code is broken.

To see this, consider two threads arriving at the if statement in getInstance at the same time. Let's consider the three options for which branches they take:

  1. They both choose the second branch. Then you create two instances and your code no longer implements a singleton.
  2. They both choose the first branch. Then you never create an instance.
  3. One choose the first and the other chooses the second. But since there is no lock in the first branch, the thread that takes that route can read AObject before the other thread has written it.

Personally I'd use double-checked locking if I had to implement your getInstance function.

Zingale answered 17/5, 2013 at 13:0 Comment(10)
Yes, you are right, i get so excited about using InterlockedCompare, that i not even realise about this part. thank youJessamyn
The best way to do this, is put all code inside the critical section context, is this right?Jessamyn
You can use double checked locking which does work in Delphi on x86/x64. I added a link in my answer to another answer here (one of mine) that covers this topic in more detail.Zingale
what about this cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.htmlJessamyn
Read the answer that I linked to. Essentially the x86 and x64 memory models are strong, and Delphi doesn't optimise reads/writes from/to globals into registers. So, DCL works in Delphi on x86/x64Zingale
Double-checked will work to me. Despite my lack of proficiency in English, you could understand what I wanted, thank you.Jessamyn
Well, it took a while. Your English seems fine. I think a little more of an explanation in the question would have helped, but I think we got there in the end.Zingale
What i really don't understand is why people was decreasing my question before understanding what i was asking.Jessamyn
let us continue this discussion in chatZingale
Thank you David, and everybody others who at least read the post and tried to help!Jessamyn
S
5

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 InterlockedCompareExchangeRelease to use release semantics (to ensure the construction of the object completes before performing the compare exchange). The problem with that is:

  • i'm not smart enough to know if Release semantics alone really will work (just cause a smarter person than me said they would, doesn't mean i know what he's talking about)
  • you're constructing an object, the memory barrier performance hit is the least of your worries (it's the thread safety)

Note: Any code released into public domain. No attribution required.

Sapsucker answered 8/7, 2014 at 16:0 Comment(3)
+1, it seems light-weight...Have you studied the possibility of reordering? Is this free of it? That's exactly what I was looking forJessamyn
You should add your answer here too: #5392607Jessamyn
@Jessamyn It's memory safe because InterlockedCompareExchangePointer erects a full memory barrier. It is entirely likely that i could get away with InterlockedCompareExchangePointerRelease semantics, but i'm too much of a chicken.Sapsucker
Z
4

Your design does not work and this can be seen even without any understanding of what InterlockedCompareExchange does. In fact, irrespective of the meaning of InterlockedCompareExchange, your code is broken.

To see this, consider two threads arriving at the if statement in getInstance at the same time. Let's consider the three options for which branches they take:

  1. They both choose the second branch. Then you create two instances and your code no longer implements a singleton.
  2. They both choose the first branch. Then you never create an instance.
  3. One choose the first and the other chooses the second. But since there is no lock in the first branch, the thread that takes that route can read AObject before the other thread has written it.

Personally I'd use double-checked locking if I had to implement your getInstance function.

Zingale answered 17/5, 2013 at 13:0 Comment(10)
Yes, you are right, i get so excited about using InterlockedCompare, that i not even realise about this part. thank youJessamyn
The best way to do this, is put all code inside the critical section context, is this right?Jessamyn
You can use double checked locking which does work in Delphi on x86/x64. I added a link in my answer to another answer here (one of mine) that covers this topic in more detail.Zingale
what about this cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.htmlJessamyn
Read the answer that I linked to. Essentially the x86 and x64 memory models are strong, and Delphi doesn't optimise reads/writes from/to globals into registers. So, DCL works in Delphi on x86/x64Zingale
Double-checked will work to me. Despite my lack of proficiency in English, you could understand what I wanted, thank you.Jessamyn
Well, it took a while. Your English seems fine. I think a little more of an explanation in the question would have helped, but I think we got there in the end.Zingale
What i really don't understand is why people was decreasing my question before understanding what i was asking.Jessamyn
let us continue this discussion in chatZingale
Thank you David, and everybody others who at least read the post and tried to help!Jessamyn

© 2022 - 2024 — McMap. All rights reserved.