0

I have static array with 100 items type of record:

TMy_Array:array[1..100] of T;

where T is:

T = record
  A: double;
  B: Date;
  C: String;
end;

I have n similar threads modifying their elements in the TMy_Array

TMy_Array[n].A; 
TMy_Array[n].B; 
TMy_Array[n].C)`. 

N is close to 100.

I also have 10 other threads that selectively modify any field TMy_Array. To achieve this safely should I use critical sections and here comes the question:

Does use only one critical section will not cause overcrowding of fighting and waiting threads to access the array?

Maybe Can I or should I apply 100 critical sections that will protect access to a particular items in T_Array and the will not block other threads?

Var
  Cs:array [1..100] of TRTLCriticalSection;

//then for n-thread:
EnterCriticalSection(CS[n]);
TMy_Array[n].A:=…;
TMy_Array[n].B:=…;
TMy_Array[n].C:=…;
LeaveCriticalSection(CS[n]);

Is this a good idea, doesn't overload too much application?

And second question:

Are the TDateTime (really Double) and String atomic data? Can I read (only read) them without worry about conflicts during another thread is writing to it?

Ken White
  • 123,280
  • 14
  • 225
  • 444
Artik
  • 803
  • 14
  • 36
  • You do not mention your Delphi version. Later (D2009) versions have `TThreadList`, which has built-in thread locking of the list. I suggest to try if this is sufficient for your application first. Only one thread can operate on this list at any given time. – LU RD May 01 '13 at 19:42
  • 1
    About atomic operations. Quoting @dThorpe from here:[`Delphi: preferred way of protection using Critical Sections`](http://stackoverflow.com/a/8101926/576719). "Atomic write is meaningless if you have multivalue semantics (multiple pieces of data that need to be updated in the same operation) and your design requires that all values be coherent with each other at all times." – LU RD May 01 '13 at 20:01
  • 2
    @LURD: `TThreadList` has been around for a LONG time, at least since D5, probably even earlier. – Remy Lebeau May 01 '13 at 20:08
  • 1
    @Artik: if you need to have multiple threads access the **same** `T` instance at the same time, then yes, you need to implement some kind of locking mechanism. A single lock for the entire array may be a bottleneck, but a lock on each `T` may be overkill, too. It really depends on what you are actually trying to accomplish with your threads. – Remy Lebeau May 01 '13 at 20:10
  • 1
    @RemyLebeau, that may well be the case. I only saw it reside inside `Generics.Collections`, but now I see it's in `System.Classes` as well. Sorry for the confusion. – LU RD May 01 '13 at 20:12
  • Thank you all. It helped me look at the problem differently. Remy Lebeau: Problem connects to the IdHttp server and its clients (approximately 100) modifying TMy_Array. Can I use solution to the problem proposed by LU RD or do you suggest something else? – Artik May 02 '13 at 05:21

1 Answers1

3

Try to serialize the access to your list/array.

  • The simplest way to serialize is by using a TThreadList for keeping the records. TThreadList exists in two versions, one generic in Generics.Collections and a non-generic in Classes. All access to such a list is guarded with a lock/unlock mechanism. This approach is a good start. Measure performance, and see if there are any problem bottlenecks.

  • Another approach is to have one thread guard all list/array accesses through a thread-safe queue. Other threads trying to read/write data from the list/array sends a read/write request on the queue.

    • For the reading request, a copy of the record is sent in another queue to the requesting thread.
    • The write request is committed by the guardian thread.

    Now everything is event driven with minimum delay. No conflicts about thread safety and a clear description on causality.

    For a thread-safe queue, look at TThreadedQueue if you have Delphi-XE2 or newer.


Here is an example outlining the above described queue approach.

Uses
  Classes,SysUtils,Generics.Collections;
Type
  T = record
    A : Double;
    B : String;
  end;
var
  MyArr : array[1..100] of T;
  GuardingQueue : TThreadedQueue<TProc>;

procedure GuardingThread.Execute;
var
  aProc : TProc;
begin
  while not Terminated do
  begin
    aProc := GuardingQueue.PopItem;
    if not Assigned(aProc) then
      Exit; // Quit thread when nil is sent to the queue
    aProc(); // Execute request
  end;
end;

procedure AccessingThread.Execute;
var
  aLocalQueue : TThreadedQueue<T>;
  aT : T;
begin
  // Make sure aLocalQueue is initialized
  // To get data fom the array ...
  GuardingQueue.PushItem( // read from array
    procedure
    var
      aT : T;
    begin
      aT.A := MyArr[2].A;
      aT.B := MyArr[2].B;
      aLocalQueue.PushItem(aT);
    end
  );
  aT := aLocalQueue.PopItem; // Here is the result from the read request

  // Writing to the array ...
  GuardingQueue.PushItem( // write to array
    procedure
    begin
      MyArr[2].A := 2;
      MyArr[2].B := 'Test';
    end
  );

end;
LU RD
  • 34,438
  • 5
  • 88
  • 296
  • Thank you, it looks it resolve my problem. I'll try to use it. Unfortunately I have Delphi XE but I've looked in documentation - maybe it has TThreadedQueue. Thank you again. – Artik May 02 '13 at 05:17
  • 1
    Unfortunately, the `TThreadedQueue` in Delphi XE is broken because of several errors in `TMonitor`. See [`TThreadedQueue not capable of multiple consumers?`](http://stackoverflow.com/q/4856306/576719). – LU RD May 02 '13 at 05:37