Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Daten parallel mithilfe eines Ringbuffers wegspeichern - Critical Sect (https://www.delphipraxis.net/135267-daten-parallel-mithilfe-eines-ringbuffers-wegspeichern-critical-sect.html)

Viktorii 8. Jun 2009 08:00


Daten parallel mithilfe eines Ringbuffers wegspeichern - Cri
 
Moin zusammen.

Ich möchte eine Funktion (Get_Data) aufrufen, welche eine gewisse Zeit aktiv bleibt (z.B 500 ms) und welche gewisse Daten liefert. Unmittelbar nach derm wiederkehren soll die Funktion wieder aufgerufen werden um die nächste Daten zu liefern.

Während dessen (also z.B. in den nächsten 500 ms) sollen die Daten die die Funktion beim letzen mal geliefert hat weggespeichert werden.

Um diese zu realisieren habe ich mir einen Ringbuffer geschaffen. Desweiteren habe ich den Get_Data-Funktionsaufruf sowie das Wegspeichern der Daten in einen separaten Thread ausgelagert.

Die ganze Geschichte funktioniert soweit einwandfrei. Ich habe jedoch zwei Fragen:

1. Sind die Critical Sections im 'Ring Buffer' sinnvoll bzw. nötig?
2. Ist das Konzept so in Ordnung, oder kann man etwas besser, effizienter der eleganter lösen? Da gehe ich mal starkt von aus, da dies das erste mal ist, das ich sowas geschrieben habe. Für jegliches konstruktives Feedback bin ich sehr dankbar. :thumb:


Jetzt zur Implementierung:


Erstellung der einzelnen Objekte:
Delphi-Quellcode:
procedure TMainForm.FormCreate(Sender: TObject);
begin
  ...
  ...
  FGetDataThread := TGetDataThread.Create(TRUE);  
  FSaveDataThread := TSaveDataThread.Create(TRUE);

  FRingBuffer := TRingBuffer.Create;              

  FGetDataThread.RingBuffer := FRingBuffer;    
  FSaveDataThread.RingBuffer := FRingBuffer;    

  WM_GET_DATA_COMPLETE   := RegisterWindowMessage('{4F775E08-98FB-41b0-9852-10C7C13DD81C}');
end;


In der überschriebenen Fensterprocedure wir der Thread zum wegspeichern der Daten angestoßen, sobald Daten von den Get_Data-Thread verfügbar sind:
Delphi-Quellcode:
procedure TMainForm.WndProc(var Message: TMessage);
begin
  if (Message.Msg = WM_GET_DATA_COMPLETE) then
  begin
    // Wenn Head(-pointer) <> Tail(-pointer) -> alles OK
    if FRingBuffer.Head <> FRingBuffer.Tail then
    begin
      // SaveDataThread aufwecken um gerade bekommenen Daten auf HD zu speichern
      FSaveDataThread.Resume;
    end
    else
    begin
      // Fehler. Head hat den Tail eingeholt :-(
      MessageBox(0, pWideChar('Overrun!'), '', MB_ICONSTOP or MB_OK);
    end;
  end;
  inherited;
end;


Im GetData-Thread wird die Funktion Get_Data ausgeführt, der "Head-Pointer" wird inkrementiert und die Fensterprocedure wird benachrichtigt, dass nun Data vorliegen. Unmittelbar danach wird die Get_Data-Funktion erneut aufgerufen:
Delphi-Quellcode:
{ TGetDataThread }
procedure TGetDataThread.Execute;
begin
  while not terminated do
  begin
    ret := Get_Data(@FRingBuffer.pDataArray[FRingBuffer.Head][1]);

    FRingBuffer.IncHead;
    SendMessage(MainForm.Handle, MainForm.WM_GET_DATA_COMPLETE, 0, 0);
  end;
end;


Im SaveData-Thread werden die Daten solange weggespeichert, bis der "Tail-Pointer" den "Head-Pointer" erreicht hat. Ist dies der Fall liegen (noch) keine weiteren Daten vor:
Delphi-Quellcode:
{ TSaveDataThread }
procedure TSaveDataThread.Execute;
begin
  repeat
    repeat
      FFileStream.Write(FRingBuffer.pDataArray[FRingBuffer.Tail][1], NUMBER_OF_DATA_BYTES);
      FRingBuffer.IncTail;
    until (FRingBuffer.Tail = FRingBuffer.Head);

    suspend;
  until (Terminated);

  FreeAndNil(FFileStream);
end;


Hier nun der Ringbuffer mit den Critical Sections:
Delphi-Quellcode:
type
  TBuffer    = array[0..10, 1..65536] of word;
  TpBuffer   = ^TBuffer;

type
  TRingBuffer = Class (TObject)
  private
    FDataArray : TBuffer;
    FpDataArray : TpBuffer;
    FHead      : Integer;
    FTail      : Integer;
    FCSHead    : TCriticalSection;
    FCSTail    : TCriticalSection;
    function GetHead: Integer;
    function GetTail: Integer;
  public
    Constructor Create;
    Destructor Destroy; override;
    procedure IncHead;
    procedure IncTail;
    property Head: Integer read GetHead;
    property Tail: Integer read GetTail;
    property pDataArray: TpRolBufDataOfAllCCDs read FpDataArray;
  end;


implementation

uses
  SysUtils;




{ TRingBuffer }
constructor TRingBuffer.Create;
begin
  FpDataArray := @FDataArray;
  FHead := 0;
  FTail := 0;
  FCSHead := TCriticalSection.Create;
  FCSTail := TCriticalSection.Create;
end;

//------------------------------------------------------------------------------

destructor TRingBuffer.Destroy;
begin
  FreeAndNil(FCSHead);
  FreeAndNil(FCSTail)
end;

//------------------------------------------------------------------------------

function TRingBuffer.GetHead: Integer;
begin
  FCSHead.Enter;
  result := FHead;
  FCSHead.Leave;
end;

//------------------------------------------------------------------------------

function TRingBuffer.GetTail: Integer;
begin
  FCSTail.Enter;
  result := FTail;
  FCSTail.Leave;
end;

//------------------------------------------------------------------------------

procedure TRingBuffer.IncHead;
begin
  FCSHead.Enter;
  inc(FHead);
  if FHead > 10 then
    FHead := 0;
  FCSHead.Leave;
end;

//------------------------------------------------------------------------------

procedure TRingBuffer.IncTail;
begin
  FCSTail.Enter;
  inc(FTail);
  if FTail > 10 then
    FTail := 0;
  FCSTail.Leave;
end;
Vielen Dank im Vorrauch für jegliche Tipps, Hinweise und Verbesserungsvorschläge. :dp:

jaenicke 8. Jun 2009 08:25

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Also erst entfernst du im Delphi-Treff ein paar Minuten nach deinem Post den Text und fragst dann direkt hier nach. Willst du hier auch den Beitrag gleich wieder leeren?

Jetzt habe ich wirklich gar keine Lust mehr etwas dazu zu schreiben nachdem ich den Post drüben direkt wieder verwerfen konnte. :evil:

Viktorii 8. Jun 2009 08:32

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Zitat:

Zitat von jaenicke
Also erst entfernst du im Delphi-Treff ein paar Minuten nach deinem Post den Text und fragst dann direkt hier nach. Willst du hier auch den Beitrag gleich wieder leeren?

Wollte hier posten, habe aber aus versehen dort gepostet. Der Beitrag stand dort auch nur wenige Minuten und wurde mittlerweile entfernt. Bin leider nur ein Mensch nur mache manchmal etwas falsch. So wie Du mich hier anblökst scheint Dir sowas ja nie zu passieren. Da beneide ich Dich Drum :|

Zitat:

Zitat von jaenicke
Jetzt habe ich wirklich gar keine Lust mehr etwas dazu zu schreiben nachdem ich den Post drüben direkt wieder verwerfen konnte. :evil:

Dann lass es halt :roll:

jaenicke 8. Jun 2009 09:05

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Zitat:

Zitat von Viktorii
Wollte hier posten, habe aber aus versehen dort gepostet.

Trotzdem hättest du ihn dort ja nicht gleich leeren müssen, sowas ist kein guter Stil. Was solls, ist ja nicht mehr zu ändern.

Zitat:

Zitat von Viktorii
Bin leider nur ein Mensch nur mache manchmal etwas falsch. So wie Du mich hier anblökst scheint Dir sowas ja nie zu passieren. Da beneide ich Dich Drum :|

So meinte ich das nicht, jeder macht Fehler, aber deshalb gleich den Thread dort zu leeren statt auf Antworten zu warten... Egal.
Was ich eben meinte war, dass ich gerade einiges zu deinem Quelltext geschrieben hatte und als ich es abschicken wollte war dein Beitrag schon leer...


Zum Quelltext habe ich keine Lust das noch einmal zu schreiben.
Aber dann wollte ich noch auf Pipes hinweisen. Diese eignen sich sehr gut zur Datenweitergabe in eine Richtung, in deinem Fall vom Thread, der die Berechnungen durchführt zu dem, der die Speicherung macht. Ich weiß nicht wie gut das bei dir passt, könnte aber evtl. einen fließenden Datenstrom ermöglichen statt wie bisher alle x Millisekunden.

Denn bei einer Pipe ist das Schöne, dass der Thread jeweils einfach darauf warten kann, dass neue Daten ankommen und dann diese direkt verarbeiten kann.

sirius 8. Jun 2009 09:12

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
  1. Du brauchst hier kein RegisterWindowMessage, da du anscheinend ausschließlich in deinem eigenen Process bist und auch sonst braucht man es recht selten. Leg einfach eine Zahl bei WM_User+X fest (X als kleine natürliche Zahl).
  2. So, wie es jetzt geschrieben ist, brauchst du keine CriticalSections. Die dauern viel zu lange. Hier kommt man mit den Interlockedxxxx- Funktionen weiter. Wenn du diese bei schreibenden Vorgängen benutzt, brauchst du bei lesenden Vorgängen keine Absicherung.
  3. Das prinzipielle Vorgehen scheint so zu funktionieren (ich entdecke keinen Fehler beim Drüberschauen) und da es sehr speziell an dein Problem angepasst ist, dürfte es auch sehr effizient sein.
    Allerdings ist das nicht die prinzipielle Vorgehensweise. Diese würde zwar ein paar Performanceverluste bringen, dafür aber "sicherer" sein. Das heißt, man kann einen Synchronisationsfehler nicht so leicht übersehen, wie in deinem Code.
    Du müsstest dazu den Zugriff auf DataArray komplett kapseln. Dann liest du mit GetData in eine lokale Variable und kopierst diese gesichert nach in deinen Buffer.

Viktorii 8. Jun 2009 09:28

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Vielen Dank für Eure Antworten :!:

Über Pipes bin ich gerade am lesen... Interlockedxxxx- Funktionen werde ich mir danach anschauen...


Zitat:

Zitat von sirius
Du müsstest dazu den Zugriff auf DataArray komplett kapseln. Dann liest du mit GetData in eine lokale Variable und kopierst diese gesichert nach in deinen Buffer.


Könntest Du das evtl. etwas genauer ausführen? Das Prinzip ist mir nicht ganz klar geworden :oops:

jaenicke 8. Jun 2009 09:41

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Zitat:

Zitat von sirius
Leg einfach eine Zahl bei WM_User+X fest (X als kleine natürliche Zahl).

Um genau zu sein: WM_APP + X, denn die Werte zwischen WM_USER und WM_APP sollten nur innerhalb einer Fensterklasse verwendet werden, da sie auch in Fensterklassen vom System verwendet werden. Da hier die Nachrichten in der WndProc der MainForm abgefangen werden, könnten diese glaube ich mit solchen Nachrichten mit dem gleichen Wert kollidieren. ;-)

Viktorii 8. Jun 2009 10:13

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Zitat:

Zitat von sirius
So, wie es jetzt geschrieben ist, brauchst du keine CriticalSections. Die dauern viel zu lange. Hier kommt man mit den Interlockedxxxx- Funktionen weiter. Wenn du diese bei schreibenden Vorgängen benutzt, brauchst du bei lesenden Vorgängen keine Absicherung.

Wenn ich das jetzt richtig verstanden habe müsste es dann so aussehen:

Delphi-Quellcode:
procedure TRingBuffer.IncHead;
begin
  InterlockedIncrement(FHead);
  if FHead > 10 then
    FHead := 0;
end;
Aber bin ich damit wirklich auf der sicheren Seite?

Folgendes Szenario: InterlockedIncrement wird ausgeführt. In diesem Moment kann die Variable nicht von anderen Threads gelesen werden. Angenommen der Wert von FHead sei vor dem inkrementieren 10 und danach folglich 11. Diesen 'Überlauf' fange ich zwar mit der folgenden if-Abfrage ab, aber was passiert wenn ein anderer Thread den Wert ausliest bevor diese geschehen ist? Dann hat er den Wert 11, sollte aber 0 haben :gruebel:

jaenicke 8. Jun 2009 10:16

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Du solltest dir einmal anschauen was es da noch alles für Interlocked-Funktionen gibt. ;-)
Unter anderem InterlockedExchange, InterlockedAnd, InterlockedXor, ... ;-)
Siehe Dokumentation:
http://msdn.microsoft.com/en-us/library/ms683614.aspx

// EDIT:
Allerdings könnte ein anderer Thread trotzdem den Wert zwischen den beiden Operationen auslesen.

sirius 8. Jun 2009 10:33

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Zitat:

Zitat von Viktorii
Folgendes Szenario: InterlockedIncrement wird ausgeführt. In diesem Moment kann die Variable nicht von anderen Threads gelesen werden. Angenommen der Wert von FHead sei vor dem inkrementieren 10 und danach folglich 11. Diesen 'Überlauf' fange ich zwar mit der folgenden if-Abfrage ab, aber was passiert wenn ein anderer Thread den Wert ausliest bevor diese geschehen ist? Dann hat er den Wert 11, sollte aber 0 haben :gruebel:

Zum Nullsetzen solltest du natürlich auch eine Interlockedxxx verwenden. Und das Problem mit der 10 und der elf kannst du ja vorher abfragen.

Delphi-Quellcode:
if FHead<10 then
  InterlockedIncrement(FHead)
else
  InterlockedExchangeAdd(FHead,-9);

himitsu 8. Jun 2009 11:17

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
wenn dir 16 MB nicht zuviel Puffer sind, könntest du FHead und FTail auch einfach als Byte definieren und dann statt 0..10 auf 0..255 umsteigen, da würde die Überlaufprüfung (if FHead > 10 then) entfallen, da sie ja schon im Typ eingebaut wäre :angel:

hier würde also ein einfaches "Inc" ausreichen
Delphi-Quellcode:
procedure TRingBuffer.IncHead;
begin
  InterlockedIncrement(FHead);
end;


ansonsten würde ich besser noch den Inhalt vorm Setzen vergleichen,
Delphi-Quellcode:
procedure TRingBuffer.IncHead;
begin
  if InterlockedCompareExchange(FHead, 10, 0) <> 10 then
    InterlockedIncrement(FHead);
end;
(ich hoff ich hab das jetzt richtig gemacht, aber im Prinzip würd ich es irgendwie so machen :angel2: )

denn z.B. bei
Delphi-Quellcode:
procedure TRingBuffer.IncHead;
begin
  InterlockedIncrement(FHead);
  if FHead > 10 then
    //FHead := 0;
    InterlockedXor(FHead, 0);
end;
ist FHead hier kurzzeitig außerhalb des "zuläßigen" Bereichs und wenn gerade in der, wenn auch sehr kurzen Zeitspanne ein anderer Thread den wert ausließt, dann bekommt der ja einen "ungültigen" Wert geliefert

Viktorii 8. Jun 2009 15:14

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Nochmals vielen Dank an alle.

Habe mich vorerst für sirius' Lösung entschieden:


Delphi-Quellcode:
if FHead<10 then
  InterlockedIncrement(FHead)
else
  InterlockedExchangeAdd(FHead,-9);
Auch wenn es so jetzt sauberer ist, möchte ich die anderen Möglichkeiten auch noch verstehen.

Kann mir hierbei noch jemand auf die Sprünge helfen:

Zitat:

Zitat von sirius
Allerdings ist das nicht die prinzipielle Vorgehensweise. Diese würde zwar ein paar Performanceverluste bringen, dafür aber "sicherer" sein. Das heißt, man kann einen Synchronisationsfehler nicht so leicht übersehen, wie in deinem Code.
Du müsstest dazu den Zugriff auf DataArray komplett kapseln. Dann liest du mit GetData in eine lokale Variable und kopierst diese gesichert nach in deinen Buffer.

:?:

sirius 8. Jun 2009 18:12

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Eine vollständige Lösung sähe vielleicht folgendermaßen aus. Wie gesagt: das ist jetzt etwas schlechter von der Performance her und auch etwas größer. Deswegen würde ich bei deinem Problem auch etwas kleineres wählen. Beim Schreiben dieses Codes fielen mir aber noch zwei Probleme in deinem Code auf.
  1. Threadsteuerung mit Suspend und Resume zu machen ist nicht so gut und wird von M$ nicht empfohlen. Besser sind Events.
  2. Was machst du, wenn dein GetDataThread den SaveDatathread einholt und beide auf das gleiche Array schreiben? Bzw.: Wie verhinderst du dies?

Ich habe mal ein Beispiel versucht.
Also die Idee ist eine Klasse, welche die CS und den Zugriff auf den Inhalt verwaltet:
Delphi-Quellcode:
type
     //Erstmal packen wir das Array in eine Klasse, wenn auch noch sehr rudimentär
     TDataArray=class(TPersistent)
      public
        //ist mal nicht mit einem property gekapselt, weil ich eh nur den Pointer darauf benötige
        //evtl. wird das ja hier noch anders verwendet
        Values:array[1..65536] of word;
      protected
        //zum Kopieren der Klasse
        procedure AssignTo(Dest: TPersistent); override;
     end;

     //der Zweite Teil des Array wird eine List
     //hier eine TObjectList nur mit angpeassten Methoden, damit man nicht
     //ständig außerhalb mit dem As-Operator handeln muss
     //zudem wird noch auf Notify reagiert
     TDataList=class(TObjectList)
       private
         FonAdd:TNotifyevent;
       protected
         procedure Notify(Ptr: Pointer; Action: TListNotification); override;
         function GetItem(Index: Integer): TDataArray;
         procedure SetItem(Index: Integer; ADataArray: TDataArray);
       public
         property Items[Index: Integer]: TDataArray read GetItem write SetItem; default;
         property OnAdd:TNotifyEvent read FonAdd write FonAdd;
     end;


     //Kapselung der Liste mit einer CS
     TThreadDataList=class
       Constructor Create; reintroduce;
       Destructor Destroy; override;
     private
       FList:TDataList;
       FLock:TCriticalSection;
     public
        procedure Add(Item: TDataArray);
        function LockList: TDataList;
        procedure UnlockList;
     end;
Die "eigentliche" Klasse ist TThreadDataList. Darin enthalten ist eine Liste(TDataList). Und diese Liste beinhaltet das Array (TDataArray)
TDataArray hat nur das Array für einen Lese-Vorgang und noch die AssignTo Methode um die Klasse zu kopieren (geerbt von TPersistent).
TDataList verdeckt eigentlich nur die Methoden von TObjectList um aus den Parametern TObject TDataArray zu machen. Ist dann einfacher in der Anwendung. Außerdem wird Notify überschrieben, so dass darin reagiert werden kann, wann die Liste >10 Einträge hat, damit der älteste gelöscht wird. Außerdem kann ein Ereigniss ausgelöst werden (FonAdd)
TThreadDataList hat nur drei Methoden. Add um einen Eintrag der obigen Liste hinzuzufügen (mit CS gekapselt) und das Paar LockLsit und Unlocklist um die Liste nur bei gleichzeitiger Synchronisation zu bekommen (siehe dazu TThreadList)
Der Code hierzu ist:
Delphi-Quellcode:
function TDataList.GetItem(Index: Integer): TDataArray;
begin
  result:=inherited GetItem(Index) as TDataArray;
end;

procedure TDataList.Notify(Ptr: Pointer; Action: TListNotification);
begin
  inherited;
  if Action=lnAdded then
  begin
    while Count>10 Do Delete(0);
    if assigned(FonAdd) then FonAdd(self); //siehe TSaveDataThread.ListAdd
  end;
end;

procedure TDataList.SetItem(Index: Integer; ADataArray: TDataArray);
begin
  inherited SetItem(Index,ADataArray);
end;

{ TThreadDataList }

procedure TThreadDataList.Add(Item: TDataArray);
begin
  LockList;
  try
    FList.Add(Item);
  finally
    UnlockList;
  end;
end;

constructor TThreadDataList.Create;
begin
  FLock:=TCriticalSection.Create;
  FList:=TDataList.Create(True);
end;

destructor TThreadDataList.Destroy;
var List:TList;
begin
  LockList;
  try
    FList.Free;
    inherited;
  finally
    FLock.Free;
  end;
end;

function TThreadDataList.LockList: TDataList;
begin
  FLock.Enter;
  result:=FList;
end;


procedure TThreadDataList.UnlockList;
begin
  FLock.Leave;
end;


{ TDataArray }

procedure TDataArray.AssignTo(Dest: TPersistent);
begin
  inherited;
  (Dest as TDataArray).FValues:=FValues;
end;
Die Threads habe ich mal kurz so implementiert:
Delphi-Quellcode:
type
     //BeispielThreads:
     TDataThread=class(TThread)
        Constructor Create(CreateSuspended:Boolean; ThreadDataList:TThreadDataList); reintroduce;
      protected
        FThreadDataList:TThreadDataList;
     end;

     TGetDataThread=class(TDataThread)
      protected
        procedure Execute; override;
     end;
     TSaveDataThread=class(TDataThread)
      protected
        procedure Execute; override;
        procedure ListAdd(Sender:TObject);
      private
        FEvent:TEvent;
      public
        procedure Terminate; reintroduce;
     end;


//...

{ TDataThread }

constructor TDataThread.Create(CreateSuspended: Boolean;
  ThreadDataList: TThreadDataList);
begin
  inherited Create(CreateSuspended);
  FThreadDataList:=ThreadDataList;
end;

{ TGetDataThread }

procedure TGetDataThread.Execute;
var DataArray:TDataArray;
    List:TDataList;
begin
  while not terminated do
  begin
    DataArray:=TDataArray.Create;
    Get_Data(@DataArray.Values);
    List:=FThreadDataList.LockList;
    try
      List.Add(DataArray);
    finally
      FThreadDataList.UnlockList;
    end;
  end;
end;

{ TSaveDataThread }

procedure TSaveDataThread.Execute;
var List:TDataList;
    DataArray:TDataArray;
    isEntry:Boolean;
begin
  FEvent:=TEvent.Create(nil,false,true,'');
  List:=FThreadDataList.LockList;
  try
    List.OnAdd:=ListAdd;
  finally
    FThreadDataList.UnlockList;
  end;

  while not terminated do
  begin
    FEvent.WaitFor(infinite);

    repeat
      List:=FThreadDataList.LockList;
      try
        isEntry:=List.Count>0;
        if isEntry then
        begin
          DataArray.Assign(List.Items[0]);
          List.Delete(0);
        end;
      finally
        FThreadDataList.UnlockList;
      end;

      if isEntry then
      begin
        FFileStream.Write(DataArray.Values[1], NUMBER_OF_DATA_BYTES);
      end;

    until not isEntry;

  end;

end;

procedure TSaveDataThread.ListAdd(Sender: TObject);
begin
  FEvent.SetEvent;
end;

procedure TSaveDataThread.Terminate;
begin
  inherited;
  FEvent.SetEvent;
end;
die MEssage brauche ich gar nicht mehr, ich benutze einfach das Event. In Waitfor wartet der Thread, bis die Liste über ListAdd das Event setzt und der Durchlauf einmal beginnt, bis er wieder bei Waitfor ist.

Viktorii 9. Jun 2009 07:14

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Hallo sirius,

vielen Dank, dass Du dir die Mühe gemacht hast mir so ausführlich zu antworten. Muss mir das nachher erstmal in aller Ruhe zu Gemüte führen. Vermutlich wird da noch die ein oder andere Frage bei mir noch aufkommen :-D


Zitat:

Zitat von sirius
Threadsteuerung mit Suspend und Resume zu machen ist nicht so gut und wird von M$ nicht empfohlen. Besser sind Events.

Okay, dann müsste ich mal schauen dass ich das umstricke... Welchen Vorteil hat denn die Lösung mit Events und WaitForSingleObject bzw. Waitfor gegenüber den suspenden?


Zitat:

Zitat von sirius
Was machst du, wenn dein GetDataThread den SaveDatathread einholt und beide auf das gleiche Array schreiben? Bzw.: Wie verhinderst du dies?


Tja, das ist echt ein Problem. Wenn der GetDataThread schneller ist als der SaveDatathread wir er ihn auf kurz über lang ja auf jeden einholen, egal wie groß der Ringbuffer ist. Das prüfe ich ja in der WndProc und im Fehlerfall gebe ich eine Messagebox aus. Zusätzlich muss ich da natürlich noch ErrorHandling betreiben, was hier im Code aber nicht vorhanden ist:

Delphi-Quellcode:
procedure TMainForm.WndProc(var Message: TMessage);
begin
  if (Message.Msg = WM_GET_DATA_COMPLETE) then
  begin
    // Wenn Head(-index) <> Tail(-index) -> alles OK
    if FRingBuffer.Head <> FRingBuffer.Tail then
    begin
      // SaveDataThread aufwecken um gerade bekommenen Daten auf HD zu speichern
      FSaveDataThread.Resume;
    end
    else
    begin
      // Fehler. Head hat den Tail eingeholt :-(
      MessageBox(0, pWideChar('Overrun!'), '', MB_ICONSTOP or MB_OK);
    end;
  end;
  inherited;
end;

jaenicke 9. Jun 2009 07:17

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Zitat:

Zitat von Viktorii
Okay, dann müsste ich mal schauen dass ich das umstricke... Welchen Vorteil hat denn die Lösung mit Events und WaitForSingleObject bzw. Waitfor gegenüber den suspenden?

Wenn du den Thread von außen schlafen legst, kennst du dessen Zustand nicht unbedingt. Wenn du dem Thread nur sagst, dass er das tun soll, dann kann der ggf. noch fertig rechnen usw., oder z.B. eine offene Datei vorher schließen.

Und zum Einholen: Genau das würdest du mit Pipes vermeiden. Denn da kommen eben an einer Seite Daten rein und können am anderen abgeholt werden, mehr nicht.

sirius 9. Jun 2009 08:29

Re: Daten parallel mithilfe eines Ringbuffers wegspeichern -
 
Zu suspend:
Zitat:

Zitat von msdn
This function is primarily designed for use by debuggers. It is not intended to be used for thread synchronization. Calling SuspendThread on a thread that owns a synchronization object, such as a mutex or critical section, can lead to a deadlock if the calling thread tries to obtain a synchronization object owned by a suspended thread. To avoid this situation, a thread within an application that is not a debugger should signal the other thread to suspend itself. The target thread must be designed to watch for this signal and respond appropriately.

Du machst zwar das supend innerhalb deines Threads. Insofern könnte das sogar ok sein, aber ich würde trotzdem die Finger davon lassen.


Alle Zeitangaben in WEZ +1. Es ist jetzt 09:24 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz