Lazarus

Free Pascal => General => Topic started by: Trenatos on January 06, 2020, 05:31:04 pm

Title: Threadsafe Singleton
Post by: Trenatos on January 06, 2020, 05:31:04 pm
I initially looked into semaphores but they're deprecated.

Looking at https://wiki.freepascal.org/Singleton_Pattern there's a line towards the end that says "The implementations have a problem with multi-threading and synchronization"

So on that note, can someone point me to an example of a threadsafe singleton?

Ultimately my goal is a threadsafe database connection pool.

--

I did find http://www.nickhodges.com/MultiThreadingInDelphi/ToC.html, recommended by Thaddy in a different thread, checking that out.

And I also found this thread talking about syncobjs https://forum.lazarus.freepascal.org/index.php?topic=36540.0
Title: Re: Threadsafe Singleton
Post by: Thaddy on January 06, 2020, 06:04:38 pm
I would advise against simple singletons in database applications, but under windows it is quite easy using a named event.
Otherwise, write a small Daemon/Service and poll that.

*****
Warning: the current example on wikipedia is incorrect: https://en.wikipedia.org/wiki/Singleton_pattern#Delphi_and_Free_Pascal_implementation so do not use that.
The case is the initialization and finalization handling: that should not be there. I will put it on my todo list to write a better/working/real one.
*****
Title: Re: Threadsafe Singleton
Post by: Trenatos on January 06, 2020, 06:11:23 pm
This is for a webserver running under Linux with threaded fphttpserver.

I need a way to set up and manage a pool of database connections. A threadsafe singleton seems like the way to go, what would you suggest instead?
Title: Re: Threadsafe Singleton
Post by: Thaddy on January 06, 2020, 06:21:41 pm
Usually the webserver itself can handle such issues quite well based on the internal workings of the sockets.  And most databases can handle this too, just not lightweight embedded ones.
Title: Re: Threadsafe Singleton
Post by: BeniBela on January 06, 2020, 07:07:26 pm
For multi-threading you need some synchronization.

Take the last example

Code: Pascal  [Select][+][-]
  1.  class function TSingleton.Create: TSingleton;
  2.  begin
  3.    if Singleton = nil then
  4.      Singleton := TSingleton.Init;
  5.    Result := Singleton;
  6.  end;

and change it to:

Code: Pascal  [Select][+][-]
  1.  
  2. class function TSingleton.Create: TSingleton;
  3. var temp: TSingleton;
  4. begin
  5.   if Singleton = nil then begin
  6.    temp := TSingleton.Init;  
  7.    WriteBarrier;
  8.    if InterlockedCompareExchange(pointer(Singleton), pointer(temp), nil) <> nil then
  9.      temp.free;
  10.   end;
  11.   Result := Singleton;
  12. end;
  13.  

Now I am not sure about the barriers. InterlockedCompareExchange might already add a write barrier, and you probably do not need the write barrier anyways on x86. Perhaps you also need a read barrier for arm or alpha in case Singleton is not nil
Title: Re: Threadsafe Singleton
Post by: Trenatos on January 06, 2020, 07:20:33 pm
Thaddy, I could have each webserver client connection get its own database connection, but that's a pretty heavy process so I'd like to offload it to a pool manager to lower the overall response time per web-request.

Thanks BeniBela, I'll take a look at that as soon as I'm off work.
Generally this will run on x64 Linux, but the more systems supported the better.
Title: Re: Threadsafe Singleton
Post by: avk on January 17, 2020, 11:20:34 am
Just curious, is this the correct implementation of Singleton?
Code: Pascal  [Select][+][-]
  1. type
  2.  
  3.   TSingleton = class
  4.   strict private
  5.   class var
  6.     FInstance: TSingleton;
  7.     class constructor Init;
  8.     class destructor Done;
  9.   var
  10.     FExistFlag: LongInt;
  11.   protected
  12.     function AlreadyExists: Boolean;
  13.   public
  14.     class function NewInstance: TObject; override;
  15.     constructor Create; virtual;
  16.     procedure FreeInstance; override;
  17.   end;
  18.  
  19. ////
  20.  
  21. class constructor TSingleton.Init;
  22. begin
  23.   FInstance := nil;
  24. end;
  25.  
  26. class destructor TSingleton.Done;
  27. begin
  28.   if FInstance <> nil then
  29.     begin
  30.       FInstance.FExistFlag := 0;
  31.       FreeAndNil(FInstance);
  32.     end;
  33. end;
  34.  
  35. function TSingleton.AlreadyExists: Boolean;
  36. begin
  37.   Result := InterlockedCompareExchange(FExistFlag, 1, 0) <> 0;
  38. end;
  39.  
  40. class function TSingleton.NewInstance: TObject;
  41. var
  42.   Inst: TObject;
  43. begin
  44.   if FInstance = nil then
  45.     begin
  46.       Inst := inherited NewInstance;
  47.       WriteBarrier;
  48.       if InterlockedCompareExchange(Pointer(FInstance), Pointer(Inst), nil) <> nil then
  49.         Inst.Free;
  50.     end;
  51.   Result := FInstance;
  52. end;
  53.  
  54. constructor TSingleton.Create;
  55. begin
  56.   if AlreadyExists then
  57.     exit;
  58.   inherited;
  59. end;
  60.  
  61. procedure TSingleton.FreeInstance;
  62. begin
  63.   if FExistFlag = 0 then
  64.     inherited;
  65. end;
  66.  

Or maybe this one will be better?
Code: Pascal  [Select][+][-]
  1. type
  2.   TSingleton = class abstract
  3.   strict private
  4.   class var
  5.     FInstance: TSingleton;
  6.     class constructor Init;
  7.     class destructor Done;
  8.   var
  9.     FLock: LongInt;
  10.     FExists: Boolean;
  11.   protected
  12.     procedure Lock; inline;
  13.     procedure Unlock; inline;
  14.     procedure InternalCreate; virtual; abstract;
  15.     property  AlreadyExists: Boolean read FExists;
  16.   public
  17.     class function NewInstance: TObject; override;
  18.     constructor Create;
  19.     procedure FreeInstance; override;
  20.   end;
  21.  
  22. ////
  23.  
  24. class constructor TSingleton.Init;
  25. begin
  26.   FInstance := nil;
  27. end;
  28.  
  29. class destructor TSingleton.Done;
  30. begin
  31.   if FInstance <> nil then
  32.     begin
  33.       FInstance.FExists := False;
  34.       FreeAndNil(FInstance);
  35.     end;
  36. end;
  37.  
  38. procedure TSingleton.Lock;
  39. begin
  40.   while Boolean(InterlockedExchange(FLock, 1)) do
  41.     ThreadSwitch;
  42. end;
  43.  
  44. procedure TSingleton.Unlock;
  45. begin
  46.   InterlockedExchange(FLock, 0);
  47. end;
  48.  
  49. class function TSingleton.NewInstance: TObject;
  50. var
  51.   Inst: TObject;
  52. begin
  53.   if FInstance = nil then
  54.     begin
  55.       Inst := inherited NewInstance;
  56.       WriteBarrier;
  57.       if InterlockedCompareExchange(Pointer(FInstance), Pointer(Inst), nil) <> nil then
  58.         Inst.Free;
  59.     end;
  60.   Result := FInstance;
  61. end;
  62.  
  63. constructor TSingleton.Create;
  64. begin
  65.   Lock;
  66.   try
  67.     if AlreadyExists then
  68.       exit;
  69.     FExists := True;
  70.     InternalCreate;
  71.   finally
  72.     Unlock;
  73.   end;
  74. end;
  75.  
  76. procedure TSingleton.FreeInstance;
  77. begin
  78.   if AlreadyExists then
  79.     exit;
  80.   inherited;
  81. end;
  82.  
Title: Re: Threadsafe Singleton
Post by: BeniBela on January 17, 2020, 11:34:04 pm
The second is better. I doubt the first one will work.

However, I was trying  to avoid the lock, hence the use of Interlocked....  if you use a lock, you should use a critical section, and forget about Interlocked...

A lock/critical section is the most reliable way to implement a threadsafe singleton. But it is also the slowest. It can be made faster with double-checked locking, which only uses the lock, if the singleton has not been created.

But i want to do the entire initialization with only one global variable, FInstance. (then the code can also be used for other objects than singletons. One lock for one singleton is not a big issue, but imagine you want to lazily initialize thousands of objects, then you might need to have thousands of locks)

Hence, InterlockedCompareExchange. It only has the disadvantage that multiple threads might create a TSingleton instance, and then all threads except one thread free their own instance again

Title: Re: Threadsafe Singleton
Post by: argb32 on January 17, 2020, 11:53:22 pm
Every unit is a ready to use singleton.
Other singletons are antipattern.
Title: Re: Threadsafe Singleton
Post by: avk on January 18, 2020, 06:28:49 am
@BeniBela, thank you.
@argb32, could you please give a little more detail?
Title: Re: Threadsafe Singleton
Post by: Thaddy on January 18, 2020, 10:12:34 am
@BeniBela
Maybe drop the locking and use a TEventObject instead?
Title: Re: Threadsafe Singleton
Post by: argb32 on January 18, 2020, 01:47:06 pm
@argb32, could you please give a little more detail?

A Pascal unit can contain stuff, does initialize once and is accessible anywhere. Just as singleton. And that all is threadsafe.
Why need to reimplement all these properties with classes?
Title: Re: Threadsafe Singleton
Post by: avk on January 18, 2020, 02:57:54 pm
Just out of curiosity. I've never seen any good Singleton implementation in Pascal.
Title: Re: Threadsafe Singleton
Post by: BeniBela on January 19, 2020, 12:19:08 am
The unit always would create the singleton. The classes could only create the singleton, when it is actually used, so the program starts faster.

And I plan to use this function for non-singletons.
Title: Re: Threadsafe Singleton
Post by: jamie on January 19, 2020, 03:04:38 am
Nice fancy new age terminology , "Singleton" , Is that anything related to a Simpleton ? 8-)
Title: Re: Threadsafe Singleton
Post by: winni on January 19, 2020, 03:35:05 am
Maybe!?

I thought about electro pop: Single Tone
Title: Re: Threadsafe Singleton
Post by: avk on January 19, 2020, 05:29:55 am
@jamie, this "new age terminology" is about 25 years old. Is your watch stopped?
Title: Re: Threadsafe Singleton
Post by: dsiders on January 19, 2020, 08:25:59 am
@jamie, this "new age terminology" is about 25 years old. Is your watch stopped?

Even a broken watch is right twice per day. :)
Title: Re: Threadsafe Singleton
Post by: 440bx on January 19, 2020, 08:38:34 am
Even a broken watch is right twice per day. :)
How many times is it right per night ? ;)
TinyPortal © 2005-2018