[gelöst] TThread - prinzipielles Vorgehen

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

[gelöst] TThread - prinzipielles Vorgehen

Beitrag von Scotty »

Ich versuche mich erstmalig an Threads und stocke gerade am Design. Es gibt bisher eine Klasse TBruteForce, die eine TList mit auszuführenden Operationen enthält, im tausender Bereich. Diese sollen parallel, aber nicht alle auf einmal, ausgeführt werden. Etwas Code:

Code: Alles auswählen

type
 
  TOperation=class(TBoard) //TBoard ist ein komplexes Datenobjekt mit ein paar Funktionen
    private
    public
      constructor Create();
      class(TThread) procedure Run;
    end;
 
  TBestMove=record
    x,y,z  : byte;
    Value : word;
  end;
 
  TBruteForce=class
    private
      FBestMove   : TBestMove;
      FProcessing : TList;
    published
    public
      constructor Create;
      destructor Destroy; override;
      function ComputeMove(const PlaceBestMove:boolean): Word;
    end;
 
function TBruteForce.ComputeMove(const PlaceBestMove: boolean): Word;
begin
  for to do FProcessing.Add(TOperation.Create);
  while FProcessing.Count>0 do
  begin
     FProcessing.Run;
  end;
  PlaceBestMove;
end;
 
procedure TThread.Execute;
var Value:word;
     s:string;
begin
  s:=Operation(<FProcessing.Data>);
  Value:=Operation2(s);
  if Value>BestMove then BestMove:=FProcessing.Data;
end;
Ich fürchte, die Frage ist zu allgemein gestellt, aber vielleicht habt Ihr ja doch ein paar Tipps für mich.
TIA, Scotty.
Zuletzt geändert von Scotty am Do 6. Aug 2009, 11:25, insgesamt 1-mal geändert.

Euklid
Lazarusforum e. V.
Beiträge: 2808
Registriert: Fr 22. Sep 2006, 10:38
OS, Lazarus, FPC: Lazarus v2.0.10, FPC 3.2.0
Wohnort: Hessen
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von Euklid »

Hallo Scotty,

seit Kurzem ist die Handhabung mehrerer Threads mit FreePascal/Lazarus besonders einfach geworden: Durch die Verwendung der Unit MTProcs. Diese nimmt dem Programmierer den leidigen Teil der Threadverwaltung völlig ab. Kompakte Informationen dazu gibt es hier:
http://wiki.lazarus.freepascal.org/Parallel_procedures" onclick="window.open(this.href);return false;

In der Toolbox (Zeitschrift) für März/April wird die MTProcs sehr detailiert vorgestellt.

Wenn du auf klassische Weise (also mit Verwaltungsaufwand) Threads benutzen willst, hätte ich ein beschränkt komplexes Demo-Programm, welches ich hochladen könnte.

Viele Grüße, Alexander

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von Scotty »

Euklid hat geschrieben:hätte ich ein beschränkt komplexes Demo-Programm, welches ich hochladen könnte.
Das wäre nett. Vielleicht interessiert es ja auch andere. Der Wiki-Eintrag klingt auch interessant auf den ersten Blick. Unklar ist mir in dem Fall aber, wie ich ein Synchronize der Ergebnisse hin bekomme. In meinem Beispiel wäre das TBestMove, der von jedem Thread ausgelesen und ggf. geändert werden soll.

Euklid
Lazarusforum e. V.
Beiträge: 2808
Registriert: Fr 22. Sep 2006, 10:38
OS, Lazarus, FPC: Lazarus v2.0.10, FPC 3.2.0
Wohnort: Hessen
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von Euklid »

Ok - dann sind wahrscheinlich die Beispiele im Lazarus-Programmordner, im Unterverzeichnis

Examples --> Multithreading

etwas für dich? Hier wird der Befehl Synchronize gut demonstriert.

In unserem Demo-Programm tritt der Fall eines gemeinsamen Zugriffs nämlich nicht auf. Habe es dir trotzdem angehängt.

Viele Grüße, Euklid
Dateianhänge
4t.tar.gz
(35.03 KiB) 92-mal heruntergeladen

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6888
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von af0815 »

Ein kleines Beispiel:
Das Hauptformular eines Grundgerüsts was ich für Erklärungszwecke verwende. Da sind ein paar Buttons und Textanzeigen drauf. Ist garantiert an ein Beispiel aus Lazarus angelehnt.

Code: Alles auswählen

unit umain; 
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  ActnList, ComCtrls, ExtCtrls, ButtonPanel, Menus, uKameraThread, Buttons;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    actClose: TAction;
    actConnect: TAction;
    actDisconnect: TAction;
    ActionListMain: TActionList;
    BitBtn1: TBitBtn;
    BitBtn2: TBitBtn;
    BitBtn3: TBitBtn;
    ImageListMain: TImageList;
    PanelMain: TPanel;
    StatusBarMain: TStatusBar;
    ToolBar1: TToolBar;
    ToolButton1: TToolButton;
    ToolButton2: TToolButton;
    ToolButton3: TToolButton;
    ToolButton4: TToolButton;
    procedure actCloseExecute(Sender: TObject);
    procedure actConnectExecute(Sender: TObject);
    procedure actDisconnectExecute(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { private declarations }
    KameraThread : TKameraThread;
    bRunning : boolean;
    procedure AnzeigeStatus(Status:string);
    procedure AnzeigeDaten(Status:string);
    procedure SetConnectStatus;
  public
    { public declarations }
  end; 
 
var
  Form1: TForm1; 
 
implementation
 
{ TForm1 }
 
procedure TForm1.actCloseExecute(Sender: TObject);
begin
  // Hier gehts raus
  Close;
end;
 
procedure TForm1.actConnectExecute(Sender: TObject);
begin
  // Verbinden der Kamera
  KameraThread := TKameraThread.Create;
  KameraThread.OnShowStatus:=@AnzeigeStatus;
  KameraThread.OnShowData:= @AnzeigeDaten;
  bRunning := true;
  SetConnectStatus;
end;
 
procedure TForm1.actDisconnectExecute(Sender: TObject);
begin
  // Trennen der Kamera
  KameraThread.Terminate;
  Sleep(1000); // warten bis thread sicher gestoppt ist
  bRunning := false;
  SetConnectStatus;
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  // Initialiseren der Formulardaten
  bRunning := false;
  // Buttons richtig setzen
  SetConnectStatus;
end;
 
procedure TForm1.FormDestroy(Sender: TObject);
begin
  // Check ob beim Ausstieg wirklich alles ok ist.
  if bRunning then
  begin
    KameraThread.Terminate;
    Sleep(1000); // warten bis thread sicher gestoppt ist
  end;
end;
 
procedure TForm1.AnzeigeStatus(Status: string);
begin
  StatusBarMain.Panels[1].Text:=Status;
end;
 
procedure TForm1.AnzeigeDaten(Status: string);
begin
  StatusBarMain.Panels[2].Text:=Status;
end;
 
procedure TForm1.SetConnectStatus;
begin
  actConnect.Enabled:= not bRunning;
  actDisconnect.Enabled:= bRunning;
end;
 
 
initialization
  {$I umain.lrs}
 
end.
Hier der Thread

Code: Alles auswählen

unit ukamerathread;
 
{ $IFDEF FPC}
  { $MODE DELPHI}
{ $ENDIF}
{$mode objfpc}
{$H+}
 
 
interface
 
uses
 {$ifdef unix}
   cthreads,
 {$endif}
  Classes, SysUtils;
 
type
 
  TShowStatusEvent = procedure(Status: String) of Object;
  TShowDataEvent = procedure(Status: String) of Object;
  TShowImageEvent = procedure(Status: String) of Object;
 
  { TKameraThread }
 
  TKameraThread = class(TThread)
  private
    fStatusText : string;
    FOnShowData: TShowDataEvent;
    FOnShowImage: TShowImageEvent;
    FOnShowStatus: TShowStatusEvent;
    procedure ShowData;
    procedure ShowImage;
    procedure ShowStatus;
  protected
    procedure Execute; override;
  public
    Constructor Create;
    property OnShowData: TShowDataEvent read FOnShowData write FOnShowData;
    property OnShowImage: TShowImageEvent read FOnShowImage write FOnShowImage;
    property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
  end;
 
 
 
implementation
 
{ TKameraThread }
 
procedure TKameraThread.ShowData;
// Diese Methode wird vom MainThread ausgeführt und
//  kann deshalb auf alle GUI Elemente zugreifen
begin
  if Assigned(FOnShowData) then
  begin
    FOnShowData(fStatusText);
  end;
end;
 
procedure TKameraThread.ShowImage;
// Diese Methode wird vom MainThread ausgeführt und
//  kann deshalb auf alle GUI Elemente zugreifen
begin
  if Assigned(FOnShowImage) then
  begin
    FOnShowImage(fStatusText);
  end;
end;
 
procedure TKameraThread.ShowStatus;
// Diese Methode wird vom MainThread ausgeführt und
//  kann deshalb auf alle GUI Elemente zugreifen
begin
  if Assigned(FOnShowStatus) then
  begin
    FOnShowStatus(fStatusText);
  end;
end;
 
procedure TKameraThread.Execute;
var
  newStatus : string;
begin
  fStatusText := 'TMyThread Starting...';
  Synchronize(@ShowStatus);
  //
  fStatusText := 'TMyThread Running...';
  Synchronize(@ShowStatus);
  while (not Terminated) {and ([any condition required])} do
    begin
      //...
      //[Hier ist die Hauptscvhleife des Threads]
      newStatus := TimeToStr(now());   //Demo!!
      sleep(100);
      //...
      if NewStatus <> fStatusText then
        begin
          fStatusText := newStatus;
          Synchronize(@ShowData);
        end;
    end;
  fStatusText := 'TMyThread Terminating...';
  Synchronize(@ShowStatus);
end;
 
constructor TKameraThread.Create;
begin
  FreeOnTerminate := True;
  inherited Create(false);  //läuft immer los !!
end;
 
end.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von Scotty »

Danke Euch beiden. Ich habe die MTProcs ausprobiert und bin begeistert. Zumindest in einem ersten Versuch ließ sich das Multithreading sehr einfach einbauen, und auch die Ausführung war um ein Vielfaches besser. Wenn ich die "List out of bounds" selber verursache :oops: und irgendwie ein Synchronize eingebaut bekomme, nehme ich diese Routinen.

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von Scotty »

Ich komme nicht so recht weiter. Die MTProcs sind zwar leicht zu implementieren, aber ich bekomme bei jedem Durchlauf unterschiedliche Ergebnisse. Außerdem finde ich keine Möglichkeit zum Synchronisieren. Einen Testdurchlauf mit for i:=0 to xxx.Count-1 do xxx.Execute läuft sauber durch (single thread), aber ich hänge an der Stelle, wo mein Hauptprogramm auf mehrere parallele Threads warten muss. Also in etwa repeat if FThreadCount<4 then StartNextThread; sleep(0); until NoNextThread; wobei sleep() oder processmessages() wohl falsch sind. Jedenfalls hängt mein Programm. Wie macht man es richtig?

Euklid
Lazarusforum e. V.
Beiträge: 2808
Registriert: Fr 22. Sep 2006, 10:38
OS, Lazarus, FPC: Lazarus v2.0.10, FPC 3.2.0
Wohnort: Hessen
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von Euklid »

Hallo Scotty,
Scotty hat geschrieben:Die MTProcs sind zwar leicht zu implementieren, aber ich bekomme bei jedem Durchlauf unterschiedliche Ergebnisse.
... und das ist nicht darauf zurückzuführen, dass die Threads zu je unterschiedlichen, nicht-determinierten Zeitpunkten enden?
Außerdem finde ich keine Möglichkeit zum Synchronisieren.
Das Programm wird nach doparallel fortgesetzt, sobald alle Threads fertig sind.
Einen Testdurchlauf mit for i:=0 to xxx.Count-1 do xxx.Execute läuft sauber durch (single thread), aber ich hänge an der Stelle, wo mein Hauptprogramm auf mehrere parallele Threads warten muss. Also in etwa repeat if FThreadCount<4 then StartNextThread; sleep(0); until NoNextThread; wobei sleep() oder processmessages() wohl falsch sind. Jedenfalls hängt mein Programm. Wie macht man es richtig?


Dies erledigt die MTProcs automatisch - du kannst dem Befehl doparallel die Anzahl der zu startenden Threads direkt übergeben. Wenn du die Unit nicht verwenden willst, wird dir das von mir oben angehängte Beispiel weiterhelfen.

Viele Grüße, Euklid

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von Scotty »

Euklid hat geschrieben:... und das ist nicht darauf zurückzuführen, dass die Threads zu je unterschiedlichen, nicht-determinierten Zeitpunkten enden?
Wenn ich am Anfang der Routine einen Counter hochzähle und nach DoParallel die Summe ausgebe, steht da nicht der richtige Wert. Es werden also Operationen "verschluckt".
Euklid hat geschrieben:Das Programm wird nach doparallel fortgesetzt, sobald alle Threads fertig sind.
Wenn aus 100000 Operationen Ergebnisse geliefert werden, müssen die wahrscheinlich vorher nach dem besten selektiert werden. Jede einzelne Operation soll deshalb die Ergebnisse im Synchronize() übergeben. Dieser Punkt ließe sich aber wohl umgehen.
Euklid hat geschrieben:Dies erledigt die MTProcs automatisch - du kannst dem Befehl doparallel die Anzahl der zu startenden Threads direkt übergeben.
Deshalb wäre MTProcs auch nett gewesen. Aber irgendwas stimmt da wohl nicht. Du arbeitest mit WaitFor, in etwa so:

Code: Alles auswählen

repeat
   if (FThreadCount<FMaxThreadCount) and (FThreadIndex<FProcessing.Count) then
   begin
     TOperation(FProcessing[FThreadIndex]).Resume;
     inc(FThreadIndex);
     inc(FThreadCount);
   end;
   TOperation(FProcessing[FThreadIndex-1]).WaitFor;
  until FThreadCount=FMaxThreadCount;
Im OnTerminate mache ich ein dec(FThreadCount). Im Beispiel sollte natürlich nur ein Thread parallel laufen. Der startet auch, aber im Synchronize() hängt sich das Programm auf.

Code: Alles auswählen

type
  TBestMove=class
    Value   : word;
    Letters : TList;
  end;
  TOnAddBest = procedure(Value:word; Letters:TList) of Object;
 
procedure TBruteForce.AddBest(aValue: word; aLetters: TList);
var BestMove : TBestMove;
    aLetter  : TLetter;
    i        : integer;
begin
  BestMove:=TBestMove.Create;
  BestMove.Value:=aValue;
  BestMove.Letters:=TList.Create;
  for i:=0 to aLetters.Count-1 do
  begin
    aLetter:=TLetter.Create;
    aLetter.Assign(TLetter(aLetters[i]));
    BestMove.Letters.Add(aLetter);
  end;
  FBestMoves.Add(BestMove);
end;
Deswegen bin ich etwas ratlos :roll:

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6888
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von af0815 »

Scotty hat geschrieben:Wenn ich am Anfang der Routine einen Counter hochzähle und nach DoParallel die Summe ausgebe, steht da nicht der richtige Wert. Es werden also Operationen "verschluckt".
Ich würde eher darauf tippen, das ganz einfach nicht Threadsicher gearbeitet wurde.

Kannst du ev. das Beispiel posten/hochladen das nicht richtig arbeitet ?
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von Scotty »

Ich würde eher darauf tippen, das ganz einfach nicht Threadsicher gearbeitet wurde.
Daran hab ich auch gedacht, aber mittlerweile schon alles mehrfach kontrolliert. Leider ist der Code recht umfangreich. Da es aber sowieso Open Source werden soll, räume ich mal etwas auf und poste das Projekt dann auf Sourceforge. Kritik interessiert mich ja sowieso 8)

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von Scotty »

http://dl.sourceforge.net/sourceforge/s ... .0-RC8.zip" onclick="window.open(this.href);return false;

In ubruteforce -> computemove wird alles gestartet. Zum Ausführen ist eines der Wörterbücher nützlich (sonst gibt es nichts zu berechnen).

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

MTProcs (war: TThread - prinzipielles Vorgehen)

Beitrag von Scotty »

Jetzt habe ich das Problem an einem ganz einfachen Beispiel replizieren können.

Code: Alles auswählen

program test;
 
{$mode objfpc}{$H+}
 
uses
  {$IFDEF UNIX}
  cthreads,cmem,
  {$ENDIF}
  Classes,sysutils,mtprocs;
 
var s:string;
 
procedure DoSomethingParallel(Index: PtrInt; Data: Pointer; Item: TMultiThreadProcItem);
begin
  s:=s+inttostr(index)+',';
end;
 
var i:integer;
 
begin
  for i:=0 to 9 do
  begin
    s:='run '+inttostr(i)+': ';
    ProcThreadPool.DoParallel(@DoSomethingParallel,1,5,nil);
    writeln(s);
  end;
end.

Code: Alles auswählen

run 0: 2,3,1,5,4,
run 1: 1,5,
run 2: 1,3,4,5,2,6,
run 3: 1,3,4,5,
run 4: 1,2,3,4,5,
run 5: 1,2,3,4,5,
run 6: 1,2,3,4,5,
run 7: 1,2,3,4,5,
run 8: 1,2,3,4,5,
run 9: 1,2,3,4,5,
Der erste Durchgang ist erwartungsgemäß. Beim zweiten fehlt was, beim dritten ist was zu viel und dann scheint alles nur single threaded zu verlaufen.

Euklid
Lazarusforum e. V.
Beiträge: 2808
Registriert: Fr 22. Sep 2006, 10:38
OS, Lazarus, FPC: Lazarus v2.0.10, FPC 3.2.0
Wohnort: Hessen
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von Euklid »

Hallo Scotty,

Der Data-Zeiger deines DoParallel-Aufrufs zeigt auf nil - hier liegt denke ich das Problem. Auf die Daten, auf die die Threads gleichzeitig zugreifen, muss beim DoParallel-Aufruf der Data-Zeiger zeigen.
Habe dir mal das Examples-Verzeichnis von Matthias Gärtner angehängt. Ich bin mir nicht sicher, ob es das auch im SVN gibt. Das Beispiel parallelloops1 schafft Klarheit und wird dir vermutlich weiterhelfen.

Wenn deine parallel zu verarbeitenden Daten aus mehr als einem String bestehen, kannst du sie, wie im Beispiel parallelloops1 gemacht, mit Hilfe eines Records zusammenfassen und den Pointer des Records übergeben.

Viele Grüße, Euklid
Dateianhänge
examples.tar.gz
(8.13 KiB) 86-mal heruntergeladen

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: TThread - prinzipielles Vorgehen

Beitrag von Scotty »

In den anderen Beispielen wird der Zeiger auch nicht genutzt. Meine TList ist als Teil der Klasse bekannt und wird auch bearbeitet. Interessanterweise finde ich das gleiche Phänomen, wenn ich im Beispiel "testmp1" zweimal hintereinander Test.TestMTPSort; aufrufe.

Code: Alles auswählen

begin
  writeln('threads=',ProcThreadPool.MaxThreadCount);
  ProcThreadPool.MaxThreadCount:=8;
  Tests:=TTests.Create;
//  Tests.Test1;
//  Tests.Test2;
//  Tests.TestTwoThreads2;
//  Tests.TestRTLevent_Set_WaitFor;
//  Tests.TestMTPWaitForIndex;
//  Tests.TestMTPExceptionInStarterThread;
//  Tests.TestMTPExceptionInHelperThread;
  Tests.TestMTPSort;
  Tests.TestMTPSort;
  Tests.Free;
end.

Code: Alles auswählen

threads=4
TTests.TestMTPSort Running 1x0
TParallelSortPointerList.Sort BlockCnt=8 fBlockSize=1250000 fBlockCntPowOf2Offset=0
TParallelSortPointerList.LWTSort Index=1 sort block: 1250000 1875000 2499999
TParallelSortPointerList.LWTSort Index=3 sort block: 3750000 4375000 4999999
TParallelSortPointerList.LWTSort Index=4 sort block: 5000000 5625000 6249999
TParallelSortPointerList.LWTSort Index=2 sort block: 2500000 3125000 3749999
TParallelSortPointerList.LWTSort Index=0 sort block: 0 625000 1249999
TParallelSortPointerList.LWTSort Index=5 sort block: 6250000 6875000 7499999
TParallelSortPointerList.LWTSort Index=7 sort block: 8750000 9375000 9999999
TParallelSortPointerList.LWTSort Index=6 sort block: 7500000 8125000 8749999
TTests.TestMTPSort  6.18199976161122E+000
TTests.TestMTPSort Running 1x0
TParallelSortPointerList.Sort BlockCnt=8 fBlockSize=1250000 fBlockCntPowOf2Offset=0
TParallelSortPointerList.LWTSort Index=0 sort block: 0 625000 1249999
TTests.TestMTPSort  9.89000243134797E-001
An unhandled exception occurred at $08049DFB :
Exception : not sorted
  $08049DFB  TTESTS__MTPLOOP_TESTDOUBLEMTPSORT,  line 344 of testmtp1.lpr
  $080955D6  TPROCTHREADPOOL__DOPARALLELINTERN,  line 673 of mtprocs.pas
  $08049C93  TTESTS__TESTMTPSORT,  line 322 of testmtp1.lpr
  $08049ED2  main,  line 365 of testmtp1.lpr

Antworten