1

My application used to be mono thread, but now to increase performance we need to make it multithread.

We have Lists and ListItems in the following architecture:

TBListItem = class(TBusinessObjects)
private 
  FList : TBList;
protected
  //methods
public 
  constructor Create(AList: TBList); reintroduce;
  //other methods
  //properties... 
end;

Instead of inheritance we prefer the composition of the list.

TBList = class(TPersistent)
private 
  FItemClass : TBListItemClass; //class of TBListItem

  //this is used to AddObject(key, object) of the TStringList duplicates are no allowed   
  function  BuildKey(ArrayOfKeys: array of Variant): string;
protected
  //we use a stringlist to save the items
  FList: TStringList; 

  function  GetItem(Index: Integer): TBListItem;
  //methods like Load(); Unload(); Save();
public 
  constructor Create(ARefClassItem: TBListItemClass); reintroduce;

  //these methods use buildkey
  function Add(ArrayOfKeys: Array of Variant): TBListItem; reintroduce;
  function FindByKey(const ArrayOfKeys: array of Variant): TBListItem;

  //other methods
  //properties... 
end;

In the ADD() method do this:

var Index: Integer;
begin   
  Index:= FList.IndexOf(BuildKey(ArrayOfKeys));    
  if Index <> -1 then
    Result:= TBListItem(FList.Objects[Index])
  else 
  begin        
    Result:= FListItemClass.Create(Self);
    Result.FCodigo:= ArrayOfKeys;
    //load data from database.
    FList.AddObject(BuildKey(ArrayOfKeys), Result)
  end;    
end;

As i said, this objects are used to record cache data in runtime, but everytime we need to read/write objects in it, we have to:

EnterCriticalSection(instance of TRTLCriticalSection);
//Do Stuff
LeaveCriticalSection(Same instance);

I can't change much the architeture because there are inumerous classes that inherited from that.
When i run the processes, there are a lot of spikes in the processor graphic and a lot of downtimes too.
The system is compiled from delphi 6 compiler.
The Critical session has been created in the initialization of the unit.

Is there another way to do this ?
May I somehow at least not to lock the reading ?

Also, I have to guarantee the integrity, 2 object with the same key are not allowed.

TLama
  • 75,147
  • 17
  • 214
  • 392
EProgrammerNotFound
  • 2,403
  • 4
  • 28
  • 59
  • Delphi has a multi-read single-write lock as well - http://docwiki.embarcadero.com/Libraries/XE3/en/System.SysUtils.TMultiReadExclusiveWriteSynchronizer, IIRC it was around with Delphi 6. – Lloyd Feb 20 '13 at 13:35
  • 1
    How could you not lock the reading? What if the write comes along whilst you are reading? The best you can do is single writer / multiple reader lock. Or RCU, but that's tricky as I understand. – David Heffernan Feb 20 '13 at 13:36
  • @Lloyd TMultiReadExclusiveWriteSynchonizer has a bug, i can't use it. – EProgrammerNotFound Feb 20 '13 at 13:41
  • @DavidHeffernan What you mean with tricky? – EProgrammerNotFound Feb 20 '13 at 13:43
  • I mean RCU is hard to get right. Since you'd have to implement it yourself, most likely you'd implement something with bugs. What is the problem with TMultiReadExclusiveWriteSynchonizer? – David Heffernan Feb 20 '13 at 13:44
  • If I wanted a single write, multiple read lock I'd use Windows SRW locks, and either not support XP at all, or switch to critical section on XP. – David Heffernan Feb 20 '13 at 13:46
  • @DavidHeffernan Requests for locks are not queued may this cause conflicts? i readed it here : [link](http://stackoverflow.com/questions/1187382/behaviour-of-tmultireadexclusivewritesynchronizer-when-promoting-read-lock-to-wr) – EProgrammerNotFound Feb 20 '13 at 13:47
  • I don't know anything about `TMultiReadExclusiveWriteSynchonizer`. I would not be surprised if it was flawed. Emba don't have a good track record when it comes to writing synchro objects. As I said, I'd use SRW. – David Heffernan Feb 20 '13 at 13:50
  • @DavidHeffernan is SRW = Slim Read/Write? may i use with delphi 6? – EProgrammerNotFound Feb 20 '13 at 13:53
  • Yes, you can. Anyway, I don't like passing of the `array of Variant` in your code. Moreover, passing it by value (without `const` keyword). Consider to use a different data type (if possible), or at least add `const` keyword to those parameters (if they are meant to be constant). – TLama Feb 20 '13 at 13:58
  • @TLama what kind of type could replace array of variant? – EProgrammerNotFound Feb 20 '13 at 14:02
  • 1
    It depends on what type are those variants. If they are just one type, then I would use just array of that type (like e.g. `array of string`) and typecast them with `VarAsType` or `VarToStr` functions before passing them to your public methods. The same way you can modify also your private methods. – TLama Feb 20 '13 at 14:53

1 Answers1

3

You are going to need to perform some synchronisation on reading. You can't let one thread mutate a data structure whilst another tries to read it. A common approach is a single writer, multiple reader lock.

Delphi comes with one of these, namely TMultiReadExclusiveWriteSynchronizer. However, I believe that its performance is poor, and since the TMonitor debacle I personally have little faith in the ability of Emba's engineers to write correct synchronization primitives.

My recommendation would be to use the Slim Reader/Writer (SRW) Lock introduced in Vista. If you still need to support XP then I'd suggest falling back to a critical section.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 3
    The SRW is implemented in the `JEDI Windows API`. See [slim-readwrite-lock-the-fast-alternative-to-tmultireadexclusivewritesynchronizer](http://blog.delphi-jedi.net/2011/09/16/slim-readwrite-lock-the-fast-alternative-to-tmultireadexclusivewritesynchronizer/). There is a comment about a shared lock can't be elevated to an exclusive lock. – LU RD Feb 20 '13 at 14:06