You've already forked lazarus-ccr
added multithreadprocs
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@627 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
46
components/multithreadprocs/examples/testmtp1.lpi
Normal file
46
components/multithreadprocs/examples/testmtp1.lpi
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0"?>
|
||||
<CONFIG>
|
||||
<ProjectOptions>
|
||||
<PathDelim Value="/"/>
|
||||
<Version Value="6"/>
|
||||
<General>
|
||||
<SessionStorage Value="InProjectDir"/>
|
||||
<MainUnit Value="0"/>
|
||||
<TargetFileExt Value=""/>
|
||||
</General>
|
||||
<VersionInfo>
|
||||
<ProjectVersion Value=""/>
|
||||
</VersionInfo>
|
||||
<PublishOptions>
|
||||
<Version Value="2"/>
|
||||
<IgnoreBinaries Value="False"/>
|
||||
<IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
|
||||
<ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/>
|
||||
</PublishOptions>
|
||||
<RunParams>
|
||||
<local>
|
||||
<FormatVersion Value="1"/>
|
||||
<LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
|
||||
</local>
|
||||
</RunParams>
|
||||
<RequiredPackages Count="1">
|
||||
<Item1>
|
||||
<PackageName Value="MultiThreadProcsLaz"/>
|
||||
<MinVersion Valid="True"/>
|
||||
<DefaultFilename Value="../multithreadprocslaz.lpk"/>
|
||||
</Item1>
|
||||
</RequiredPackages>
|
||||
<Units Count="1">
|
||||
<Unit0>
|
||||
<Filename Value="testmtp1.lpr"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
</Unit0>
|
||||
</Units>
|
||||
</ProjectOptions>
|
||||
<CompilerOptions>
|
||||
<Version Value="8"/>
|
||||
<Other>
|
||||
<CompilerPath Value="$(CompPath)"/>
|
||||
</Other>
|
||||
</CompilerOptions>
|
||||
</CONFIG>
|
367
components/multithreadprocs/examples/testmtp1.lpr
Normal file
367
components/multithreadprocs/examples/testmtp1.lpr
Normal file
@@ -0,0 +1,367 @@
|
||||
program TestMTP1;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
uses
|
||||
{$IFDEF UNIX}
|
||||
cthreads, cmem,
|
||||
{$ENDIF}
|
||||
Math, SysUtils, Classes, MTProcs, MTPUtils, MultiThreadProcsLaz;
|
||||
|
||||
type
|
||||
|
||||
{ TTestItem }
|
||||
|
||||
TTestItem = class
|
||||
private
|
||||
FIndex: int64;
|
||||
public
|
||||
property Index: int64 read FIndex;
|
||||
constructor Create(NewIndex: int64);
|
||||
end;
|
||||
|
||||
{ TTests }
|
||||
|
||||
TTests = class
|
||||
public
|
||||
procedure Work(Seconds: integer);
|
||||
|
||||
// RTLeventSetEvent, RTLeventWaitFor
|
||||
procedure TestRTLevent_Set_WaitFor;
|
||||
|
||||
// single thread test
|
||||
procedure TestSingleThread;
|
||||
procedure MTPLoop_TestSingleThread(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
|
||||
// two threads test: run once
|
||||
procedure TestTwoThreads1;
|
||||
procedure MTPLoop_TestTwoThreads1(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
|
||||
// 0 runs two seconds,
|
||||
// 1 runs a second then waits for 0 then runs a second
|
||||
// 2 runs a second then waits for 1
|
||||
// 3 waits for 0
|
||||
// 4 waits for 1
|
||||
// 5 waits for 2
|
||||
procedure TestMTPWaitForIndex;
|
||||
procedure MTPLoop_TestMTPWaitForIndex(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
|
||||
// two threads test: various run times
|
||||
procedure TestMTPTwoThreads2;
|
||||
procedure MTPLoop_TestTwoThreads2(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
|
||||
// test exception in starter thread
|
||||
procedure TestMTPExceptionInStarterThread;
|
||||
procedure MTPLoop_TestExceptionInStarterThread(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
|
||||
// test exception in helper thread
|
||||
procedure TestMTPExceptionInHelperThread;
|
||||
procedure MTPLoop_TestExceptionInHelperThread(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
|
||||
// test parallel sort
|
||||
procedure TestMTPSort;
|
||||
procedure MTPLoop_TestDoubleMTPSort(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
end;
|
||||
|
||||
{ TTestItem }
|
||||
|
||||
constructor TTestItem.Create(NewIndex: int64);
|
||||
begin
|
||||
FIndex:=NewIndex;
|
||||
end;
|
||||
|
||||
{ TTests }
|
||||
|
||||
procedure TTests.Work(Seconds: integer);
|
||||
var
|
||||
Start: TDateTime;
|
||||
begin
|
||||
Start:=Now;
|
||||
while (Now-Start)*86400<Seconds do if GetCurrentDir='' then ;
|
||||
end;
|
||||
|
||||
procedure TTests.TestRTLevent_Set_WaitFor;
|
||||
var
|
||||
e: PRTLEvent;
|
||||
begin
|
||||
e:=RTLEventCreate;
|
||||
RTLeventSetEvent(e);
|
||||
RTLeventWaitFor(e);
|
||||
RTLeventdestroy(e);
|
||||
end;
|
||||
|
||||
procedure TTests.TestSingleThread;
|
||||
begin
|
||||
ProcThreadPool.DoParallel(@MTPLoop_TestSingleThread,1,3,nil,1);
|
||||
end;
|
||||
|
||||
procedure TTests.MTPLoop_TestSingleThread(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
begin
|
||||
writeln('TTests.MTPLoop_TestSingleThread Index=',Index);
|
||||
end;
|
||||
|
||||
procedure TTests.TestTwoThreads1;
|
||||
begin
|
||||
WriteLn('TTests.TestTwoThreads1 START');
|
||||
ProcThreadPool.DoParallel(@MTPLoop_TestTwoThreads1,1,2,nil,2);
|
||||
WriteLn('TTests.TestTwoThreads1 END');
|
||||
end;
|
||||
|
||||
procedure TTests.MTPLoop_TestTwoThreads1(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
for i:=1 to 3 do begin
|
||||
WriteLn('TTests.MTPLoop_TestTwoThreads1 Index=',Index,' ',i);
|
||||
Work(1);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TTests.TestMTPWaitForIndex;
|
||||
var
|
||||
IndexStates: PInteger;
|
||||
begin
|
||||
ProcThreadPool.MaxThreadCount:=8;
|
||||
IndexStates:=nil;
|
||||
GetMem(IndexStates,SizeOf(Integer)*10);
|
||||
FillByte(IndexStates^,SizeOf(Integer)*10,0);
|
||||
WriteLn('TTests.TestMTPWaitForIndex START');
|
||||
ProcThreadPool.DoParallel(@MTPLoop_TestMTPWaitForIndex,0,5,IndexStates);
|
||||
FreeMem(IndexStates);
|
||||
WriteLn('TTests.TestMTPWaitForIndex END');
|
||||
end;
|
||||
|
||||
procedure TTests.MTPLoop_TestMTPWaitForIndex(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
// 0 runs two seconds,
|
||||
// 1 runs a second then waits for 0 then runs a second
|
||||
// 2 runs a second then waits for 1
|
||||
// 3 waits for 0
|
||||
// 4 waits for 1
|
||||
// 5 waits for 2
|
||||
|
||||
procedure WaitFor(OtherIndex: PtrInt);
|
||||
begin
|
||||
WriteLn('TTests.MTPLoop_TestMTPWaitForIndex Index='+IntToStr(Index)+' waiting for '+IntToStr(OtherIndex)+' ...');
|
||||
Item.WaitForIndex(OtherIndex);
|
||||
WriteLn('TTests.MTPLoop_TestMTPWaitForIndex Index='+IntToStr(Index)+' waited for '+IntToStr(OtherIndex)+'. working ...');
|
||||
if PInteger(Data)[OtherIndex]<>2 then begin
|
||||
WriteLn('TTests.MTPLoop_TestMTPWaitForIndex Index='+IntToStr(Index)+' ERROR: waited for '+IntToStr(OtherIndex)+' failed: OtherState='+IntToStr(PInteger(Data)[OtherIndex]));
|
||||
end;
|
||||
end;
|
||||
|
||||
begin
|
||||
WriteLn('TTests.MTPLoop_TestMTPWaitForIndex Index='+IntToStr(Index)+' START');
|
||||
if PInteger(Data)[Index]<>0 then begin
|
||||
WriteLn('TTests.MTPLoop_TestMTPWaitForIndex Index='+IntToStr(Index)+' ERROR: IndexState='+IntToStr(PInteger(Data)[Index]));
|
||||
end;
|
||||
PInteger(Data)[Index]:=1;
|
||||
case Index of
|
||||
0: Work(2);
|
||||
1:begin
|
||||
Work(1);
|
||||
WaitFor(0);
|
||||
Work(1);
|
||||
end;
|
||||
2:begin
|
||||
Work(1);
|
||||
WaitFor(1);
|
||||
end;
|
||||
3:begin
|
||||
WaitFor(0);
|
||||
end;
|
||||
4:begin
|
||||
WaitFor(1);
|
||||
end;
|
||||
5:begin
|
||||
WaitFor(2);
|
||||
end;
|
||||
end;
|
||||
WriteLn('TTests.MTPLoop_TestMTPWaitForIndex Index='+IntToStr(Index)+' END');
|
||||
PInteger(Data)[Index]:=2;
|
||||
end;
|
||||
|
||||
procedure TTests.TestMTPTwoThreads2;
|
||||
begin
|
||||
WriteLn('TTests.TestMTPTwoThreads1 START');
|
||||
ProcThreadPool.DoParallel(@MTPLoop_TestTwoThreads2,1,6,nil,2);
|
||||
WriteLn('TTests.TestMTPTwoThreads1 END');
|
||||
end;
|
||||
|
||||
procedure TTests.MTPLoop_TestTwoThreads2(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
for i:=1 to (Index mod 3)+1 do begin
|
||||
WriteLn('TTests.MTPLoop_TestTwoThreads1 Index=',Index,' i=',i,' ID=',PtrUint(GetThreadID));
|
||||
Work(1);
|
||||
end;
|
||||
end;
|
||||
|
||||
type
|
||||
TMyException = class(Exception);
|
||||
|
||||
procedure TTests.TestMTPExceptionInStarterThread;
|
||||
var
|
||||
IndexStates: PInteger;
|
||||
begin
|
||||
WriteLn('TTests.TestMTPExceptionInStarterThread START');
|
||||
ProcThreadPool.MaxThreadCount:=8;
|
||||
IndexStates:=nil;
|
||||
GetMem(IndexStates,SizeOf(Integer)*10);
|
||||
FillByte(IndexStates^,SizeOf(Integer)*10,0);
|
||||
try
|
||||
ProcThreadPool.DoParallel(@MTPLoop_TestExceptionInStarterThread,1,3,IndexStates,2);
|
||||
except
|
||||
on E: Exception do begin
|
||||
WriteLn('TTests.TestMTPExceptionInHelperThread E.ClassName=',E.ClassName,' E.Message=',E.Message);
|
||||
end;
|
||||
end;
|
||||
FreeMem(IndexStates);
|
||||
WriteLn('TTests.TestMTPExceptionInStarterThread END');
|
||||
end;
|
||||
|
||||
procedure TTests.MTPLoop_TestExceptionInStarterThread(Index: PtrInt;
|
||||
Data: Pointer; Item: TMultiThreadProcItem);
|
||||
begin
|
||||
WriteLn('TTests.MTPLoop_TestExceptionInStarterThread START Index='+IntToStr(Index));
|
||||
if PInteger(Data)[Index]<>0 then
|
||||
WriteLn('TTests.MTPLoop_TestExceptionInStarterThread Index='+IntToStr(Index)+' ERROR: IndexState='+IntToStr(PInteger(Data)[Index]));
|
||||
PInteger(Data)[Index]:=1;
|
||||
case Index of
|
||||
1:
|
||||
begin
|
||||
// Main Thread
|
||||
Work(1);
|
||||
WriteLn('TTests.MTPLoop_TestExceptionInStarterThread raising exception in Index='+IntToStr(Index)+' ...');
|
||||
raise Exception.Create('Exception in starter thread');
|
||||
end;
|
||||
else
|
||||
Work(Index);
|
||||
end;
|
||||
PInteger(Data)[Index]:=2;
|
||||
WriteLn('TTests.MTPLoop_TestExceptionInStarterThread END Index='+IntToStr(Index));
|
||||
end;
|
||||
|
||||
procedure TTests.TestMTPExceptionInHelperThread;
|
||||
var
|
||||
IndexStates: PInteger;
|
||||
begin
|
||||
WriteLn('TTests.TestMTPExceptionInHelperThread START');
|
||||
ProcThreadPool.MaxThreadCount:=8;
|
||||
IndexStates:=nil;
|
||||
GetMem(IndexStates,SizeOf(Integer)*10);
|
||||
FillByte(IndexStates^,SizeOf(Integer)*10,0);
|
||||
try
|
||||
ProcThreadPool.DoParallel(@MTPLoop_TestExceptionInHelperThread,1,3,IndexStates,2);
|
||||
except
|
||||
on E: Exception do begin
|
||||
WriteLn('TTests.TestMTPExceptionInHelperThread E.ClassName=',E.ClassName,' E.Message=',E.Message);
|
||||
end;
|
||||
end;
|
||||
FreeMem(IndexStates);
|
||||
WriteLn('TTests.TestMTPExceptionInHelperThread END');
|
||||
end;
|
||||
|
||||
procedure TTests.MTPLoop_TestExceptionInHelperThread(Index: PtrInt;
|
||||
Data: Pointer; Item: TMultiThreadProcItem);
|
||||
begin
|
||||
WriteLn('TTests.MTPLoop_TestExceptionInHelperThread START Index='+IntToStr(Index));
|
||||
if PInteger(Data)[Index]<>0 then
|
||||
WriteLn('TTests.MTPLoop_TestExceptionInHelperThread Index='+IntToStr(Index)+' ERROR: IndexState='+IntToStr(PInteger(Data)[Index]));
|
||||
PInteger(Data)[Index]:=1;
|
||||
case Index of
|
||||
2:
|
||||
begin
|
||||
// Helper Thread 2
|
||||
Work(1);
|
||||
WriteLn('TTests.MTPLoop_TestExceptionInHelperThread raising exception in Index='+IntToStr(Index)+' ...');
|
||||
raise TMyException.Create('Exception in helper thread');
|
||||
end;
|
||||
else
|
||||
Work(Index+1);
|
||||
end;
|
||||
PInteger(Data)[Index]:=2;
|
||||
WriteLn('TTests.MTPLoop_TestExceptionInHelperThread END Index='+IntToStr(Index));
|
||||
end;
|
||||
|
||||
function CompareTestItems(Data1, Data2: Pointer): integer;
|
||||
begin
|
||||
if TTestItem(Data1).Index>TTestItem(Data2).Index then
|
||||
Result:=1
|
||||
else if TTestItem(Data1).Index<TTestItem(Data2).Index then
|
||||
Result:=-1
|
||||
else
|
||||
Result:=0;
|
||||
end;
|
||||
|
||||
procedure TTests.TestMTPSort;
|
||||
var
|
||||
OuterLoop: Integer;
|
||||
InnerLoop: Integer;
|
||||
begin
|
||||
OuterLoop:=1;
|
||||
InnerLoop:=0;
|
||||
if Paramcount=1 then begin
|
||||
InnerLoop:=StrToInt(ParamStr(1));
|
||||
end else if Paramcount=2 then begin
|
||||
OuterLoop:=StrToInt(ParamStr(1));
|
||||
InnerLoop:=StrToInt(ParamStr(2));
|
||||
end;
|
||||
writeln('TTests.TestMTPSort Running ',OuterLoop,'x',InnerLoop);
|
||||
ProcThreadPool.DoParallel(@MTPLoop_TestDoubleMTPSort,1,OuterLoop,@InnerLoop);
|
||||
end;
|
||||
|
||||
procedure TTests.MTPLoop_TestDoubleMTPSort(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
var
|
||||
i: Integer;
|
||||
List: TFPList;
|
||||
t: double;
|
||||
begin
|
||||
// create an unsorted list of values
|
||||
List:=TFPList.Create;
|
||||
for i:=1 to 10000000 do List.Add(TTestItem.Create(Random(99999999999)));
|
||||
//QuickSort(List,0,List.Count-1,@AnsiCompareText);
|
||||
|
||||
t:=Now;
|
||||
ParallelSortFPList(List,@CompareTestItems,PInteger(Data)^);
|
||||
t:=Now-t;
|
||||
writeln('TTests.TestMTPSort ',t*86400);
|
||||
// check
|
||||
sleep(1);
|
||||
for i:=0 to List.Count-2 do
|
||||
if CompareTestItems(List[i],List[i+1])>0 then raise Exception.Create('not sorted');
|
||||
|
||||
for i:=0 to List.Count-1 do
|
||||
TObject(List[i]).Free;
|
||||
List.Free;
|
||||
end;
|
||||
|
||||
var
|
||||
Tests: TTests;
|
||||
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.Free;
|
||||
end.
|
||||
|
90
components/multithreadprocs/mtpcpu.pas
Normal file
90
components/multithreadprocs/mtpcpu.pas
Normal file
@@ -0,0 +1,90 @@
|
||||
{ System depending code for light weight threads.
|
||||
|
||||
This file is part of the Free Pascal run time library.
|
||||
|
||||
Copyright (C) 2008 Mattias Gaertner mattias@freepascal.org
|
||||
|
||||
See the file COPYING.FPC, included in this distribution,
|
||||
for details about the copyright.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
**********************************************************************}
|
||||
unit MTPCPU;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
{$IF defined(windows)}
|
||||
uses Windows;
|
||||
{$ELSEIF defined(freebsd) or defined(darwin)}
|
||||
uses ctypes, sysctl;
|
||||
{$ELSEIF defined(linux)}
|
||||
{$linklib c}
|
||||
uses ctypes;
|
||||
{$ENDIF}
|
||||
|
||||
function GetSystemThreadCount: integer;
|
||||
|
||||
implementation
|
||||
|
||||
{$IFDEF Linux}
|
||||
const _SC_NPROCESSORS_ONLN = 83;
|
||||
function sysconf(i: cint): clong; cdecl; external name 'sysconf';
|
||||
{$ENDIF}
|
||||
|
||||
function GetSystemThreadCount: integer;
|
||||
// returns a good default for the number of threads on this system
|
||||
{$IF defined(windows)}
|
||||
//returns total number of processors available to system including logical hyperthreaded processors
|
||||
var
|
||||
i: Integer;
|
||||
ProcessAffinityMask, SystemAffinityMask: DWORD;
|
||||
Mask: DWORD;
|
||||
SystemInfo: SYSTEM_INFO;
|
||||
begin
|
||||
if GetProcessAffinityMask(GetCurrentProcess, ProcessAffinityMask, SystemAffinityMask)
|
||||
then begin
|
||||
Result := 0;
|
||||
for i := 0 to 31 do begin
|
||||
Mask := 1 shl i;
|
||||
if (ProcessAffinityMask and Mask)<>0 then
|
||||
inc(Result);
|
||||
end;
|
||||
end else begin
|
||||
//can't get the affinity mask so we just report the total number of processors
|
||||
GetSystemInfo(SystemInfo);
|
||||
Result := SystemInfo.dwNumberOfProcessors;
|
||||
end;
|
||||
end;
|
||||
{$ELSEIF defined(UNTESTEDsolaris)}
|
||||
begin
|
||||
t = sysconf(_SC_NPROC_ONLN);
|
||||
end;
|
||||
{$ELSEIF defined(freebsd) or defined(darwin)}
|
||||
var
|
||||
mib: array[0..1] of cint;
|
||||
len: cint;
|
||||
t: cint;
|
||||
begin
|
||||
mib[0] := CTL_HW;
|
||||
mib[1] := HW_NCPU;
|
||||
len := sizeof(t);
|
||||
fpsysctl(pchar(@mib), 2, @t, @len, Nil, 0);
|
||||
Result:=t;
|
||||
end;
|
||||
{$ELSEIF defined(linux)}
|
||||
begin
|
||||
Result:=sysconf(_SC_NPROCESSORS_ONLN);
|
||||
end;
|
||||
{$ELSE}
|
||||
begin
|
||||
Result:=1;
|
||||
end;
|
||||
{$ENDIF}
|
||||
|
||||
end.
|
||||
|
796
components/multithreadprocs/mtprocs.pas
Normal file
796
components/multithreadprocs/mtprocs.pas
Normal file
@@ -0,0 +1,796 @@
|
||||
{ Unit for light weight threads.
|
||||
|
||||
This file is part of the Free Pascal run time library.
|
||||
|
||||
Copyright (C) 2008 Mattias Gaertner mattias@freepascal.org
|
||||
|
||||
See the file COPYING.FPC, included in this distribution,
|
||||
for details about the copyright.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
**********************************************************************}
|
||||
{
|
||||
Abstract:
|
||||
Light weight threads.
|
||||
This unit provides methods to easily run a procedure/method with several
|
||||
threads at once.
|
||||
}
|
||||
unit MTProcs;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
{$inline on}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, MTPCPU;
|
||||
|
||||
type
|
||||
TProcThreadGroup = class;
|
||||
TProcThreadPool = class;
|
||||
|
||||
{ TMultiThreadProcItem }
|
||||
|
||||
TMTPThreadState = (
|
||||
mtptsNone,
|
||||
mtptsActive,
|
||||
mtptsWaitingForIndex,
|
||||
mtptsWaitingFailed,
|
||||
mtptsInactive,
|
||||
mtptsTerminated
|
||||
);
|
||||
|
||||
TMultiThreadProcItem = class
|
||||
private
|
||||
FGroup: TProcThreadGroup;
|
||||
FIndex: PtrInt;
|
||||
FWaitingForIndexEnd: PtrInt;
|
||||
FWaitingForIndexStart: PtrInt;
|
||||
fWaitForPool: PRTLEvent;
|
||||
FState: TMTPThreadState;
|
||||
public
|
||||
destructor Destroy; override;
|
||||
function WaitForIndexRange(StartIndex, EndIndex: PtrInt): boolean;
|
||||
function WaitForIndex(Index: PtrInt): boolean; inline;
|
||||
property Index: PtrInt read FIndex;
|
||||
property Group: TProcThreadGroup read FGroup;
|
||||
property WaitingForIndexStart: PtrInt read FWaitingForIndexStart;
|
||||
property WaitingForIndexEnd: PtrInt read FWaitingForIndexEnd;
|
||||
end;
|
||||
|
||||
{ TProcThread }
|
||||
|
||||
TMTPThreadList = (
|
||||
mtptlPool,
|
||||
mtptlGroup
|
||||
);
|
||||
|
||||
TProcThread = class(TThread)
|
||||
private
|
||||
FItem: TMultiThreadProcItem;
|
||||
FNext, FPrev: array[TMTPThreadList] of TProcThread;
|
||||
procedure AddToList(var First: TProcThread; ListType: TMTPThreadList); inline;
|
||||
procedure RemoveFromList(var First: TProcThread; ListType: TMTPThreadList); inline;
|
||||
procedure Terminating(aPool: TProcThreadPool; E: Exception);
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
procedure Execute; override;
|
||||
property Item: TMultiThreadProcItem read FItem;
|
||||
end;
|
||||
|
||||
TMTMethod = procedure(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem) of object;
|
||||
TMTProcedure = procedure(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
|
||||
{ TProcThreadGroup
|
||||
Each task creates a new group of threads.
|
||||
A group can either need more threads or it has finished and waits for its
|
||||
threads to end.
|
||||
The thread that created the group is not in the list FFirstThread. }
|
||||
|
||||
TMTPGroupState = (
|
||||
mtpgsNone,
|
||||
mtpgsNeedThreads, // the groups waiting for more threads to help
|
||||
mtpgsFinishing, // the groups waiting for its threads to finish
|
||||
mtpgsException // there was an exception => close asap
|
||||
);
|
||||
|
||||
TProcThreadGroup = class
|
||||
private
|
||||
FEndIndex: PtrInt;
|
||||
FFirstRunningIndex: PtrInt;
|
||||
FLastRunningIndex: PtrInt;
|
||||
FStarterItem: TMultiThreadProcItem;
|
||||
FMaxThreads: PtrInt;
|
||||
FPool: TProcThreadPool;
|
||||
FStartIndex: PtrInt;
|
||||
FTaskData: Pointer;
|
||||
FNext, FPrev: TProcThreadGroup;
|
||||
FState: TMTPGroupState;
|
||||
FTaskMethod: TMTMethod;
|
||||
FFirstThread: TProcThread;
|
||||
FTaskProcdure: TMTProcedure;
|
||||
FThreadCount: PtrInt;
|
||||
FException: Exception;
|
||||
procedure AddToList(var First: TProcThreadGroup; ListType: TMTPGroupState); inline;
|
||||
procedure RemoveFromList(var First: TProcThreadGroup); inline;
|
||||
function NeedMoreThreads: boolean; inline;
|
||||
procedure AddThread(AThread: TProcThread);
|
||||
procedure RemoveThread(AThread: TProcThread); inline;
|
||||
procedure Run(Index: PtrInt; Data: Pointer; Item: TMultiThreadProcItem); inline;
|
||||
procedure IndexComplete(Index: PtrInt);
|
||||
procedure WakeThreadsWaitingForIndex;
|
||||
function HasFinishedIndex(aStartIndex, aEndIndex: PtrInt): boolean;
|
||||
procedure EnterExceptionState(E: Exception);
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
property Pool: TProcThreadPool read FPool;
|
||||
property StartIndex: PtrInt read FStartIndex;
|
||||
property EndIndex: PtrInt read FEndIndex;
|
||||
property FirstRunningIndex: PtrInt read FFirstRunningIndex; // first started
|
||||
property LastRunningIndex: PtrInt read FLastRunningIndex; // last started
|
||||
property TaskData: Pointer read FTaskData;
|
||||
property TaskMethod: TMTMethod read FTaskMethod;
|
||||
property TaskProcdure: TMTProcedure read FTaskProcdure;
|
||||
property MaxThreads: PtrInt read FMaxThreads;
|
||||
property StarterItem: TMultiThreadProcItem read FStarterItem;
|
||||
end;
|
||||
|
||||
{ TLightWeightThreadPool
|
||||
Group 0 are the inactive threads }
|
||||
|
||||
TProcThreadPool = class
|
||||
private
|
||||
FMaxThreadCount: PtrInt;
|
||||
FThreadCount: PtrInt;
|
||||
FFirstInactiveThread: TProcThread;
|
||||
FFirstActiveThread: TProcThread;
|
||||
FFirstTerminatedThread: TProcThread;
|
||||
FFirstGroupNeedThreads: TProcThreadGroup;
|
||||
FFirstGroupFinishing: TProcThreadGroup;
|
||||
FCritSection: TRTLCriticalSection;
|
||||
FDestroying: boolean;
|
||||
procedure SetMaxThreadCount(const AValue: PtrInt);
|
||||
procedure CleanTerminatedThreads;
|
||||
procedure DoParallelIntern(const AMethod: TMTMethod;
|
||||
const AProc: TMTProcedure;
|
||||
StartIndex, EndIndex: PtrInt;
|
||||
Data: Pointer = nil; MaxThreads: PtrInt = 0);
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
procedure EnterPoolCriticalSection; inline;
|
||||
procedure LeavePoolCriticalSection; inline;
|
||||
|
||||
procedure DoParallel(const AMethod: TMTMethod;
|
||||
StartIndex, EndIndex: PtrInt;
|
||||
Data: Pointer = nil; MaxThreads: PtrInt = 0); inline;
|
||||
procedure DoParallel(const AProc: TMTProcedure;
|
||||
StartIndex, EndIndex: PtrInt;
|
||||
Data: Pointer = nil; MaxThreads: PtrInt = 0); inline;
|
||||
public
|
||||
property MaxThreadCount: PtrInt read FMaxThreadCount write SetMaxThreadCount;
|
||||
property ThreadCount: PtrInt read FThreadCount;
|
||||
end;
|
||||
|
||||
var
|
||||
ProcThreadPool: TProcThreadPool = nil;
|
||||
|
||||
implementation
|
||||
|
||||
{ TMultiThreadProcItem }
|
||||
|
||||
destructor TMultiThreadProcItem.Destroy;
|
||||
begin
|
||||
if fWaitForPool<>nil then begin
|
||||
RTLeventdestroy(fWaitForPool);
|
||||
fWaitForPool:=nil;
|
||||
end;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
function TMultiThreadProcItem.WaitForIndexRange(
|
||||
StartIndex, EndIndex: PtrInt): boolean;
|
||||
var
|
||||
aPool: TProcThreadPool;
|
||||
begin
|
||||
//WriteLn('TLightWeightThreadItem.WaitForIndexRange START Index='+IntToStr(Index)+' StartIndex='+IntToStr(StartIndex)+' EndIndex='+IntToStr(EndIndex));
|
||||
if (EndIndex>=Index) then exit(false);
|
||||
if EndIndex<StartIndex then exit(true);
|
||||
if Group=nil then exit(true); // a single threaded group has no group object
|
||||
// multi threaded group
|
||||
aPool:=Group.Pool;
|
||||
if aPool.FDestroying then exit(false); // no more wait allowed
|
||||
aPool.EnterPoolCriticalSection;
|
||||
try
|
||||
if Group.FState=mtpgsException then begin
|
||||
//WriteLn('TLightWeightThreadItem.WaitForIndexRange Index='+IntToStr(Index)+', Group closing because of error');
|
||||
exit(false);
|
||||
end;
|
||||
if Group.HasFinishedIndex(StartIndex,EndIndex) then begin
|
||||
//WriteLn('TLightWeightThreadItem.WaitForIndexRange Index='+IntToStr(Index)+', range already finished');
|
||||
exit(true);
|
||||
end;
|
||||
FState:=mtptsWaitingForIndex;
|
||||
FWaitingForIndexStart:=StartIndex;
|
||||
FWaitingForIndexEnd:=EndIndex;
|
||||
if fWaitForPool=nil then
|
||||
fWaitForPool:=RTLEventCreate;
|
||||
RTLeventResetEvent(fWaitForPool);
|
||||
finally
|
||||
aPool.LeavePoolCriticalSection;
|
||||
end;
|
||||
//WriteLn('TLightWeightThreadItem.WaitForIndexRange '+IntToStr(Index)+' waiting ... ');
|
||||
RTLeventWaitFor(fWaitForPool);
|
||||
Result:=FState=mtptsActive;
|
||||
FState:=mtptsActive;
|
||||
//WriteLn('TLightWeightThreadItem.WaitForIndexRange END '+IntToStr(Index));
|
||||
end;
|
||||
|
||||
function TMultiThreadProcItem.WaitForIndex(Index: PtrInt): boolean; inline;
|
||||
begin
|
||||
Result:=WaitForIndexRange(Index,Index);
|
||||
end;
|
||||
|
||||
{ TProcThread }
|
||||
|
||||
procedure TProcThread.AddToList(var First: TProcThread;
|
||||
ListType: TMTPThreadList);
|
||||
begin
|
||||
FNext[ListType]:=First;
|
||||
if FNext[ListType]<>nil then
|
||||
FNext[ListType].FPrev[ListType]:=Self;
|
||||
First:=Self;
|
||||
end;
|
||||
|
||||
procedure TProcThread.RemoveFromList(var First: TProcThread;
|
||||
ListType: TMTPThreadList);
|
||||
begin
|
||||
if First=Self then
|
||||
First:=FNext[ListType];
|
||||
if FNext[ListType]<>nil then
|
||||
FNext[ListType].FPrev[ListType]:=FPrev[ListType];
|
||||
if FPrev[ListType]<>nil then
|
||||
FPrev[ListType].FNext[ListType]:=FNext[ListType];
|
||||
FNext[ListType]:=nil;
|
||||
FPrev[ListType]:=nil;
|
||||
end;
|
||||
|
||||
procedure TProcThread.Terminating(aPool: TProcThreadPool;
|
||||
E: Exception);
|
||||
begin
|
||||
aPool.EnterPoolCriticalSection;
|
||||
try
|
||||
// remove from group
|
||||
if Item.FGroup<>nil then begin
|
||||
// an exception occured
|
||||
Item.FGroup.EnterExceptionState(E);
|
||||
Item.FGroup.RemoveThread(Self);
|
||||
Item.FGroup:=nil;
|
||||
end;
|
||||
// move to pool's terminated threads
|
||||
case Item.FState of
|
||||
mtptsActive: RemoveFromList(aPool.FFirstActiveThread,mtptlPool);
|
||||
mtptsInactive: RemoveFromList(aPool.FFirstInactiveThread,mtptlPool);
|
||||
end;
|
||||
AddToList(aPool.FFirstTerminatedThread,mtptlPool);
|
||||
Item.FState:=mtptsTerminated;
|
||||
finally
|
||||
aPool.LeavePoolCriticalSection;
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TProcThread.Create;
|
||||
begin
|
||||
inherited Create(true);
|
||||
fItem:=TMultiThreadProcItem.Create;
|
||||
fItem.fWaitForPool:=RTLEventCreate;
|
||||
end;
|
||||
|
||||
destructor TProcThread.Destroy;
|
||||
begin
|
||||
FreeAndNil(FItem);
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
procedure TProcThread.Execute;
|
||||
var
|
||||
aPool: TProcThreadPool;
|
||||
Group: TProcThreadGroup;
|
||||
ok: Boolean;
|
||||
E: Exception;
|
||||
begin
|
||||
Group:=Item.Group;
|
||||
aPool:=Group.Pool;
|
||||
ok:=false;
|
||||
try
|
||||
repeat
|
||||
// work
|
||||
Group.Run(Item.Index,Group.TaskData,Item);
|
||||
|
||||
aPool.EnterPoolCriticalSection;
|
||||
try
|
||||
Group.IndexComplete(Item.Index);
|
||||
|
||||
// find next work
|
||||
if Group.LastRunningIndex<Group.EndIndex then begin
|
||||
// next index of group
|
||||
inc(Group.FLastRunningIndex);
|
||||
Item.FIndex:=Group.FLastRunningIndex;
|
||||
end else begin
|
||||
// remove from group
|
||||
RemoveFromList(Group.FFirstThread,mtptlGroup);
|
||||
dec(Group.FThreadCount);
|
||||
Item.FGroup:=nil;
|
||||
Group:=nil;
|
||||
if aPool.FFirstGroupNeedThreads<>nil then begin
|
||||
// add to new group
|
||||
aPool.FFirstGroupNeedThreads.AddThread(Self);
|
||||
Group:=Item.Group;
|
||||
end else begin
|
||||
// mark inactive
|
||||
RemoveFromList(aPool.FFirstActiveThread,mtptlPool);
|
||||
AddToList(aPool.FFirstInactiveThread,mtptlPool);
|
||||
Item.FState:=mtptsInactive;
|
||||
RTLeventResetEvent(Item.fWaitForPool);
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
aPool.LeavePoolCriticalSection;
|
||||
end;
|
||||
// wait for new work
|
||||
if Item.FState=mtptsInactive then
|
||||
RTLeventWaitFor(Item.fWaitForPool);
|
||||
until Group=nil;
|
||||
ok:=true;
|
||||
except
|
||||
// stop the exception and store it
|
||||
E:=Exception(AcquireExceptionObject);
|
||||
Terminating(aPool,E);
|
||||
end;
|
||||
if ok then
|
||||
Terminating(aPool,nil);
|
||||
end;
|
||||
|
||||
{ TProcThreadGroup }
|
||||
|
||||
procedure TProcThreadGroup.AddToList(var First: TProcThreadGroup;
|
||||
ListType: TMTPGroupState);
|
||||
begin
|
||||
FNext:=First;
|
||||
if FNext<>nil then
|
||||
FNext.FPrev:=Self;
|
||||
First:=Self;
|
||||
FState:=ListType;
|
||||
end;
|
||||
|
||||
procedure TProcThreadGroup.RemoveFromList(
|
||||
var First: TProcThreadGroup);
|
||||
begin
|
||||
if First=Self then
|
||||
First:=FNext;
|
||||
if FNext<>nil then
|
||||
FNext.FPrev:=FPrev;
|
||||
if FPrev<>nil then
|
||||
FPrev.FNext:=FNext;
|
||||
FNext:=nil;
|
||||
FPrev:=nil;
|
||||
FState:=mtpgsNone;
|
||||
end;
|
||||
|
||||
function TProcThreadGroup.NeedMoreThreads: boolean;
|
||||
begin
|
||||
Result:=(FLastRunningIndex<FEndIndex) and (FThreadCount<FMaxThreads)
|
||||
and (FState<>mtpgsException);
|
||||
end;
|
||||
|
||||
procedure TProcThreadGroup.AddThread(AThread: TProcThread);
|
||||
begin
|
||||
AThread.Item.FGroup:=Self;
|
||||
AThread.AddToList(FFirstThread,mtptlGroup);
|
||||
inc(FThreadCount);
|
||||
inc(FLastRunningIndex);
|
||||
AThread.Item.FIndex:=FLastRunningIndex;
|
||||
if not NeedMoreThreads then begin
|
||||
RemoveFromList(Pool.FFirstGroupNeedThreads);
|
||||
AddToList(Pool.FFirstGroupFinishing,mtpgsFinishing);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TProcThreadGroup.RemoveThread(AThread: TProcThread);
|
||||
begin
|
||||
AThread.RemoveFromList(FFirstThread,mtptlGroup);
|
||||
dec(FThreadCount);
|
||||
end;
|
||||
|
||||
procedure TProcThreadGroup.Run(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem); inline;
|
||||
begin
|
||||
if Assigned(FTaskProcdure) then
|
||||
FTaskProcdure(Index,Data,Item)
|
||||
else
|
||||
FTaskMethod(Index,Data,Item);
|
||||
end;
|
||||
|
||||
procedure TProcThreadGroup.IndexComplete(Index: PtrInt);
|
||||
var
|
||||
AThread: TProcThread;
|
||||
NewFirstRunningThread: PtrInt;
|
||||
begin
|
||||
// update FirstRunningIndex
|
||||
NewFirstRunningThread:=FStarterItem.Index;
|
||||
AThread:=FFirstThread;
|
||||
while AThread<>nil do begin
|
||||
if (NewFirstRunningThread>aThread.Item.Index)
|
||||
and (aThread.Item.Index<>Index) then
|
||||
NewFirstRunningThread:=aThread.Item.Index;
|
||||
aThread:=aThread.FNext[mtptlGroup];
|
||||
end;
|
||||
FFirstRunningIndex:=NewFirstRunningThread;
|
||||
// wake up threads (Note: do this even if FFirstRunningIndex has not changed)
|
||||
WakeThreadsWaitingForIndex;
|
||||
end;
|
||||
|
||||
procedure TProcThreadGroup.WakeThreadsWaitingForIndex;
|
||||
var
|
||||
aThread: TProcThread;
|
||||
begin
|
||||
if FState<>mtpgsException then begin
|
||||
// wake up waiting threads
|
||||
aThread:=FFirstThread;
|
||||
while aThread<>nil do begin
|
||||
if (aThread.Item.FState=mtptsWaitingForIndex)
|
||||
and HasFinishedIndex(aThread.Item.WaitingForIndexStart,
|
||||
aThread.Item.WaitingForIndexEnd)
|
||||
then begin
|
||||
// wake up the thread
|
||||
aThread.Item.FState:=mtptsActive;
|
||||
RTLeventSetEvent(aThread.Item.fWaitForPool);
|
||||
end;
|
||||
aThread:=aThread.FNext[mtptlGroup];
|
||||
end;
|
||||
if (FStarterItem.FState=mtptsWaitingForIndex)
|
||||
and HasFinishedIndex(FStarterItem.WaitingForIndexStart,FStarterItem.WaitingForIndexEnd)
|
||||
then begin
|
||||
// wake up the starter thread of this group
|
||||
FStarterItem.FState:=mtptsActive;
|
||||
RTLeventSetEvent(FStarterItem.fWaitForPool);
|
||||
end;
|
||||
end else begin
|
||||
// end group: wake up waiting threads
|
||||
aThread:=FFirstThread;
|
||||
while aThread<>nil do begin
|
||||
if (aThread.Item.FState=mtptsWaitingForIndex)
|
||||
then begin
|
||||
// end group: wake up the thread
|
||||
aThread.Item.FState:=mtptsWaitingFailed;
|
||||
RTLeventSetEvent(aThread.Item.fWaitForPool);
|
||||
end;
|
||||
aThread:=aThread.FNext[mtptlGroup];
|
||||
end;
|
||||
if (FStarterItem.FState=mtptsWaitingForIndex)
|
||||
then begin
|
||||
// end group: wake up the starter thread of this group
|
||||
FStarterItem.FState:=mtptsWaitingFailed;
|
||||
RTLeventSetEvent(FStarterItem.fWaitForPool);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TProcThreadGroup.HasFinishedIndex(
|
||||
aStartIndex, aEndIndex: PtrInt): boolean;
|
||||
var
|
||||
AThread: TProcThread;
|
||||
begin
|
||||
// test the finished range
|
||||
if FFirstRunningIndex>aEndIndex then exit(true);
|
||||
// test the unfinished range
|
||||
if FLastRunningIndex<aEndIndex then exit(false);
|
||||
// test the active range
|
||||
AThread:=FFirstThread;
|
||||
while AThread<>nil do begin
|
||||
if (AThread.Item.Index>=aStartIndex)
|
||||
and (AThread.Item.Index<=aEndIndex) then
|
||||
exit(false);
|
||||
AThread:=AThread.FNext[mtptlGroup];
|
||||
end;
|
||||
if (FStarterItem.Index>=aStartIndex)
|
||||
and (FStarterItem.Index<=aEndIndex) then
|
||||
exit(false);
|
||||
Result:=true;
|
||||
end;
|
||||
|
||||
procedure TProcThreadGroup.EnterExceptionState(E: Exception);
|
||||
begin
|
||||
if FState=mtpgsException then exit;
|
||||
case FState of
|
||||
mtpgsFinishing: RemoveFromList(Pool.FFirstGroupFinishing);
|
||||
mtpgsNeedThreads: RemoveFromList(Pool.FFirstGroupNeedThreads);
|
||||
end;
|
||||
FState:=mtpgsException;
|
||||
FException:=E;
|
||||
WakeThreadsWaitingForIndex;
|
||||
end;
|
||||
|
||||
constructor TProcThreadGroup.Create;
|
||||
begin
|
||||
FStarterItem:=TMultiThreadProcItem.Create;
|
||||
FStarterItem.FGroup:=Self;
|
||||
end;
|
||||
|
||||
destructor TProcThreadGroup.Destroy;
|
||||
begin
|
||||
FreeAndNil(FStarterItem);
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
{ TProcThreadPool }
|
||||
|
||||
procedure TProcThreadPool.SetMaxThreadCount(const AValue: PtrInt);
|
||||
begin
|
||||
if FMaxThreadCount=AValue then exit;
|
||||
if AValue<1 then raise Exception.Create('TLightWeightThreadPool.SetMaxThreadCount');
|
||||
FMaxThreadCount:=AValue;
|
||||
end;
|
||||
|
||||
procedure TProcThreadPool.CleanTerminatedThreads;
|
||||
var
|
||||
AThread: TProcThread;
|
||||
begin
|
||||
while FFirstTerminatedThread<>nil do begin
|
||||
AThread:=FFirstTerminatedThread;
|
||||
AThread.RemoveFromList(FFirstTerminatedThread,mtptlPool);
|
||||
AThread.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TProcThreadPool.Create;
|
||||
begin
|
||||
FMaxThreadCount:=GetSystemThreadCount;
|
||||
if FMaxThreadCount<1 then
|
||||
FMaxThreadCount:=1;
|
||||
InitCriticalSection(FCritSection);
|
||||
end;
|
||||
|
||||
destructor TProcThreadPool.Destroy;
|
||||
|
||||
procedure WakeWaitingStarterItems(Group: TProcThreadGroup);
|
||||
begin
|
||||
while Group<>nil do begin
|
||||
if Group.StarterItem.FState=mtptsWaitingForIndex then begin
|
||||
Group.StarterItem.FState:=mtptsWaitingFailed;
|
||||
RTLeventSetEvent(Group.StarterItem.fWaitForPool);
|
||||
end;
|
||||
Group:=Group.FNext;
|
||||
end;
|
||||
end;
|
||||
|
||||
var
|
||||
AThread: TProcThread;
|
||||
begin
|
||||
FDestroying:=true;
|
||||
// wake up all waiting threads
|
||||
EnterPoolCriticalSection;
|
||||
try
|
||||
AThread:=FFirstActiveThread;
|
||||
while AThread<>nil do begin
|
||||
if aThread.Item.FState=mtptsWaitingForIndex then begin
|
||||
aThread.Item.FState:=mtptsWaitingFailed;
|
||||
RTLeventSetEvent(AThread.Item.fWaitForPool);
|
||||
end;
|
||||
AThread:=AThread.FNext[mtptlPool];
|
||||
end;
|
||||
WakeWaitingStarterItems(FFirstGroupNeedThreads);
|
||||
WakeWaitingStarterItems(FFirstGroupFinishing);
|
||||
finally
|
||||
LeavePoolCriticalSection;
|
||||
end;
|
||||
|
||||
// wait for all active threads to become inactive
|
||||
while FFirstActiveThread<>nil do
|
||||
Sleep(10);
|
||||
|
||||
// wake up all inactive threads (without new work they will terminate)
|
||||
EnterPoolCriticalSection;
|
||||
try
|
||||
AThread:=FFirstInactiveThread;
|
||||
while AThread<>nil do begin
|
||||
RTLeventSetEvent(AThread.Item.fWaitForPool);
|
||||
AThread:=AThread.FNext[mtptlPool];
|
||||
end;
|
||||
finally
|
||||
LeavePoolCriticalSection;
|
||||
end;
|
||||
|
||||
// wait for all threads to terminate
|
||||
while FFirstInactiveThread<>nil do
|
||||
Sleep(10);
|
||||
|
||||
// free threads
|
||||
CleanTerminatedThreads;
|
||||
|
||||
DoneCriticalsection(FCritSection);
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
procedure TProcThreadPool.EnterPoolCriticalSection;
|
||||
begin
|
||||
EnterCriticalsection(FCritSection);
|
||||
end;
|
||||
|
||||
procedure TProcThreadPool.LeavePoolCriticalSection;
|
||||
begin
|
||||
LeaveCriticalsection(FCritSection);
|
||||
end;
|
||||
|
||||
procedure TProcThreadPool.DoParallel(const AMethod: TMTMethod;
|
||||
StartIndex, EndIndex: PtrInt; Data: Pointer; MaxThreads: PtrInt);
|
||||
begin
|
||||
if not Assigned(AMethod) then exit;
|
||||
DoParallelIntern(AMethod,nil,StartIndex,EndIndex,Data,MaxThreads);
|
||||
end;
|
||||
|
||||
procedure TProcThreadPool.DoParallel(const AProc: TMTProcedure;
|
||||
StartIndex, EndIndex: PtrInt; Data: Pointer; MaxThreads: PtrInt);
|
||||
begin
|
||||
if not Assigned(AProc) then exit;
|
||||
DoParallelIntern(nil,AProc,StartIndex,EndIndex,Data,MaxThreads);
|
||||
end;
|
||||
|
||||
procedure TProcThreadPool.DoParallelIntern(const AMethod: TMTMethod;
|
||||
const AProc: TMTProcedure;
|
||||
StartIndex, EndIndex: PtrInt; Data: Pointer; MaxThreads: PtrInt);
|
||||
var
|
||||
Group: TProcThreadGroup;
|
||||
i: PtrInt;
|
||||
AThread: TProcThread;
|
||||
NewThread: Boolean;
|
||||
Item: TMultiThreadProcItem;
|
||||
HelperThreadException: Exception;
|
||||
begin
|
||||
if (StartIndex>EndIndex) then exit; // nothing to do
|
||||
if FDestroying then raise Exception.Create('Pool destroyed');
|
||||
|
||||
if (MaxThreads>MaxThreadCount) or (MaxThreads<=0) then
|
||||
MaxThreads:=MaxThreadCount;
|
||||
if (StartIndex=EndIndex) or (MaxThreads<=1) then begin
|
||||
// single threaded
|
||||
Item:=TMultiThreadProcItem.Create;
|
||||
try
|
||||
for i:=StartIndex to EndIndex do begin
|
||||
Item.FIndex:=i;
|
||||
AMethod(i,Data,Item);
|
||||
end;
|
||||
finally
|
||||
Item.Free;
|
||||
end;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// create a new group
|
||||
Group:=TProcThreadGroup.Create;
|
||||
Group.FPool:=Self;
|
||||
Group.FTaskData:=Data;
|
||||
Group.FTaskMethod:=AMethod;
|
||||
Group.FTaskProcdure:=AProc;
|
||||
Group.FStartIndex:=StartIndex;
|
||||
Group.FEndIndex:=EndIndex;
|
||||
Group.FFirstRunningIndex:=StartIndex;
|
||||
Group.FLastRunningIndex:=StartIndex;
|
||||
Group.FMaxThreads:=MaxThreads;
|
||||
Group.FThreadCount:=1;
|
||||
Group.FStarterItem.FState:=mtptsActive;
|
||||
Group.FStarterItem.FIndex:=StartIndex;
|
||||
HelperThreadException:=nil;
|
||||
try
|
||||
// start threads
|
||||
EnterPoolCriticalSection;
|
||||
try
|
||||
Group.AddToList(FFirstGroupNeedThreads,mtpgsNeedThreads);
|
||||
while Group.NeedMoreThreads do begin
|
||||
AThread:=FFirstInactiveThread;
|
||||
NewThread:=false;
|
||||
if AThread<>nil then begin
|
||||
AThread.RemoveFromList(FFirstInactiveThread,mtptlPool);
|
||||
end else if FThreadCount<FMaxThreadCount then begin
|
||||
AThread:=TProcThread.Create;
|
||||
if Assigned(AThread.FatalException) then
|
||||
raise AThread.FatalException;
|
||||
NewThread:=true;
|
||||
inc(FThreadCount);
|
||||
end else begin
|
||||
break;
|
||||
end;
|
||||
// add to Group
|
||||
Group.AddThread(AThread);
|
||||
// start thread
|
||||
AThread.AddToList(FFirstActiveThread,mtptlPool);
|
||||
AThread.Item.FState:=mtptsActive;
|
||||
if NewThread then
|
||||
AThread.Resume
|
||||
else
|
||||
RTLeventSetEvent(AThread.Item.fWaitForPool);
|
||||
end;
|
||||
finally
|
||||
LeavePoolCriticalSection;
|
||||
end;
|
||||
|
||||
// run until no more Index left
|
||||
i:=StartIndex;
|
||||
repeat
|
||||
Group.FStarterItem.FIndex:=i;
|
||||
Group.Run(i,Data,Group.FStarterItem);
|
||||
|
||||
EnterPoolCriticalSection;
|
||||
try
|
||||
Group.IndexComplete(i);
|
||||
if (Group.FLastRunningIndex<Group.EndIndex) and (Group.FState<>mtpgsException)
|
||||
then begin
|
||||
inc(Group.FLastRunningIndex);
|
||||
i:=Group.FLastRunningIndex;
|
||||
end else begin
|
||||
i:=StartIndex;
|
||||
end;
|
||||
finally
|
||||
LeavePoolCriticalSection;
|
||||
end;
|
||||
until i=StartIndex;
|
||||
finally
|
||||
// wait for Group to finish
|
||||
if Group.FFirstThread<>nil then begin
|
||||
EnterPoolCriticalSection;
|
||||
try
|
||||
Group.FStarterItem.FState:=mtptsInactive;
|
||||
Group.FStarterItem.fIndex:=EndIndex;// needed for Group.HasFinishedIndex
|
||||
// wake threads waiting for starter thread to finish
|
||||
if Group.FStarterItem.FState<>mtptsInactive then
|
||||
Group.EnterExceptionState(nil)
|
||||
else
|
||||
Group.WakeThreadsWaitingForIndex;
|
||||
finally
|
||||
LeavePoolCriticalSection;
|
||||
end;
|
||||
// waiting with exponential spin lock
|
||||
i:=0;
|
||||
while Group.FFirstThread<>nil do begin
|
||||
sleep(i);
|
||||
i:=i*2+1;
|
||||
if i>30 then i:=30;
|
||||
end;
|
||||
end;
|
||||
// remove group from pool
|
||||
EnterPoolCriticalSection;
|
||||
try
|
||||
case Group.FState of
|
||||
mtpgsNeedThreads: Group.RemoveFromList(FFirstGroupNeedThreads);
|
||||
mtpgsFinishing: Group.RemoveFromList(FFirstGroupFinishing);
|
||||
end;
|
||||
finally
|
||||
LeavePoolCriticalSection;
|
||||
end;
|
||||
HelperThreadException:=Group.FException;
|
||||
Group.Free;
|
||||
// free terminated threads (terminated, because of exceptions)
|
||||
CleanTerminatedThreads;
|
||||
end;
|
||||
// if the exception occured in a helper thread raise it now
|
||||
if HelperThreadException<>nil then
|
||||
raise HelperThreadException;
|
||||
end;
|
||||
|
||||
initialization
|
||||
ProcThreadPool:=TProcThreadPool.Create;
|
||||
|
||||
finalization
|
||||
ProcThreadPool.Free;
|
||||
ProcThreadPool:=nil;
|
||||
|
||||
end.
|
||||
|
200
components/multithreadprocs/mtputils.pas
Normal file
200
components/multithreadprocs/mtputils.pas
Normal file
@@ -0,0 +1,200 @@
|
||||
{ Utilities using light weight threads.
|
||||
|
||||
This file is part of the Free Pascal run time library.
|
||||
|
||||
Copyright (C) 2008 Mattias Gaertner mattias@freepascal.org
|
||||
|
||||
See the file COPYING.FPC, included in this distribution,
|
||||
for details about the copyright.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
**********************************************************************}
|
||||
{
|
||||
Abstract:
|
||||
|
||||
}
|
||||
unit MTPUtils;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, MTProcs;
|
||||
|
||||
type
|
||||
|
||||
{ TParallelSortPointerList }
|
||||
|
||||
TParallelSortPointerList = class
|
||||
protected
|
||||
fBlockSize: PtrInt;
|
||||
fBlockCntPowOf2Offset: PtrInt;
|
||||
FMergeBuffer: PPointer;
|
||||
procedure MTPSort(Index: PtrInt; Data: Pointer; Item: TMultiThreadProcItem);
|
||||
public
|
||||
List: PPointer;
|
||||
Count: PtrInt;
|
||||
Compare: TListSortCompare;
|
||||
BlockCnt: PtrInt;
|
||||
constructor Create(aList: PPointer; aCount: PtrInt; const aCompare: TListSortCompare;
|
||||
MaxThreadCount: integer = 0);
|
||||
procedure Sort;
|
||||
end;
|
||||
|
||||
procedure ParallelSortFPList(List: TFPList; const Compare: TListSortCompare;
|
||||
MaxThreadCount: integer = 0);
|
||||
|
||||
implementation
|
||||
|
||||
procedure ParallelSortFPList(List: TFPList; const Compare: TListSortCompare;
|
||||
MaxThreadCount: integer = 0);
|
||||
var
|
||||
Sorter: TParallelSortPointerList;
|
||||
begin
|
||||
if List.Count<=1 then exit;
|
||||
Sorter:=TParallelSortPointerList.Create(@List.List[0],List.Count,Compare,
|
||||
MaxThreadCount);
|
||||
try
|
||||
Sorter.Sort;
|
||||
finally
|
||||
Sorter.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TParallelSortPointerList }
|
||||
|
||||
procedure TParallelSortPointerList.MTPSort(Index: PtrInt; Data: Pointer;
|
||||
Item: TMultiThreadProcItem);
|
||||
|
||||
procedure MergeSort(L, M, R: PtrInt; Recursive: boolean);
|
||||
var
|
||||
Src1: PtrInt;
|
||||
Src2: PtrInt;
|
||||
Dest1: PtrInt;
|
||||
begin
|
||||
if R-L<=1 then begin
|
||||
// sort lists of 1 and 2 items directly
|
||||
if L<R then begin
|
||||
if Compare(List[L],List[R])>0 then begin
|
||||
FMergeBuffer[L]:=List[L];
|
||||
List[L]:=List[R];
|
||||
List[R]:=FMergeBuffer[L];
|
||||
end;
|
||||
end;
|
||||
exit;
|
||||
end;
|
||||
// sort recursively
|
||||
if Recursive then begin
|
||||
MergeSort(L,(L+M) div 2,M-1,true);
|
||||
MergeSort(M,(M+R+1) div 2,R,true);
|
||||
end;
|
||||
// merge both blocks
|
||||
Src1:=L;
|
||||
Src2:=M;
|
||||
Dest1:=L;
|
||||
repeat
|
||||
if (Src1<M)
|
||||
and ((Src2>R) or (Compare(List[Src1],List[Src2])<=0)) then begin
|
||||
FMergeBuffer[Dest1]:=List[Src1];
|
||||
inc(Dest1);
|
||||
inc(Src1);
|
||||
end else if (Src2<=R) then begin
|
||||
FMergeBuffer[Dest1]:=List[Src2];
|
||||
inc(Dest1);
|
||||
inc(Src2);
|
||||
end else
|
||||
break;
|
||||
until false;
|
||||
// write the mergebuffer back
|
||||
Src1:=L;
|
||||
Dest1:=l;
|
||||
while Src1<=R do begin
|
||||
List[Dest1]:=FMergeBuffer[Src1];
|
||||
inc(Src1);
|
||||
inc(Dest1);
|
||||
end;
|
||||
end;
|
||||
|
||||
var
|
||||
L, M, R: PtrInt;
|
||||
i: integer;
|
||||
NormIndex: Integer;
|
||||
Range: integer;
|
||||
MergeIndex: Integer;
|
||||
begin
|
||||
L:=fBlockSize*Index;
|
||||
R:=L+fBlockSize-1; // middle block
|
||||
if R>=Count then
|
||||
R:=Count-1; // last block
|
||||
WriteLn('TParallelSortPointerList.LWTSort Index=',Index,' sort block: ',L,' ',(L+R+1) div 2,' ',R);
|
||||
MergeSort(L,(L+R+1) div 2,R,true);
|
||||
|
||||
// merge
|
||||
// 0 1 2 3 4 5 6 7
|
||||
// \/ \/ \/ \/
|
||||
// \/ \/
|
||||
// \/
|
||||
// For example: BlockCnt = 5 => Index in 0..4
|
||||
// fBlockCntPowOf2Offset = 3 (=8-5)
|
||||
// NormIndex = Index + 3 => NormIndex in 3..7
|
||||
i:=0;
|
||||
NormIndex:=Index+fBlockCntPowOf2Offset;
|
||||
repeat
|
||||
Range:=1 shl i;
|
||||
if NormIndex and Range=0 then break;
|
||||
// merge left and right block(s)
|
||||
MergeIndex:=NormIndex-Range-fBlockCntPowOf2Offset;
|
||||
if (MergeIndex+Range-1>=0) then begin
|
||||
// wait until left blocks have finished
|
||||
WriteLn('TParallelSortPointerList.LWTSort Index=',Index,' wait for block ',MergeIndex);
|
||||
if (MergeIndex>=0) and (not Item.WaitForIndex(MergeIndex)) then exit;
|
||||
// compute left and right block bounds
|
||||
M:=L;
|
||||
L:=(MergeIndex-Range+1)*fBlockSize;
|
||||
if L<0 then L:=0;
|
||||
WriteLn('TParallelSortPointerList.LWTSort Index=',Index,' merge blocks ',L,' ',M,' ',R);
|
||||
MergeSort(L,M,R,false);
|
||||
end;
|
||||
inc(i);
|
||||
until false;
|
||||
WriteLn('TParallelSortPointerList.LWTSort END Index='+IntToStr(Index));
|
||||
end;
|
||||
|
||||
constructor TParallelSortPointerList.Create(aList: PPointer; aCount: PtrInt;
|
||||
const aCompare: TListSortCompare; MaxThreadCount: integer);
|
||||
begin
|
||||
List:=aList;
|
||||
Count:=aCount;
|
||||
Compare:=aCompare;
|
||||
BlockCnt:=Count div 100; // at least 100 items per thread
|
||||
if BlockCnt>ProcThreadPool.MaxThreadCount then
|
||||
BlockCnt:=ProcThreadPool.MaxThreadCount;
|
||||
if (MaxThreadCount>0) and (BlockCnt>MaxThreadCount) then
|
||||
BlockCnt:=MaxThreadCount;
|
||||
if BlockCnt<1 then BlockCnt:=1;
|
||||
end;
|
||||
|
||||
procedure TParallelSortPointerList.Sort;
|
||||
begin
|
||||
if (Count<=1) then exit;
|
||||
fBlockSize:=(Count+BlockCnt-1) div BlockCnt;
|
||||
fBlockCntPowOf2Offset:=1;
|
||||
while fBlockCntPowOf2Offset<BlockCnt do
|
||||
fBlockCntPowOf2Offset:=fBlockCntPowOf2Offset*2;
|
||||
fBlockCntPowOf2Offset:=fBlockCntPowOf2Offset-BlockCnt;
|
||||
WriteLn('TParallelSortPointerList.Sort BlockCnt=',BlockCnt,' fBlockSize=',fBlockSize,' fBlockCntPowOf2Offset=',fBlockCntPowOf2Offset);
|
||||
GetMem(FMergeBuffer,SizeOf(Pointer)*Count);
|
||||
try
|
||||
ProcThreadPool.DoParallel(@MTPSort,0,BlockCnt-1);
|
||||
finally
|
||||
FreeMem(FMergeBuffer);
|
||||
FMergeBuffer:=nil;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
43
components/multithreadprocs/multithreadprocslaz.lpk
Normal file
43
components/multithreadprocs/multithreadprocslaz.lpk
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0"?>
|
||||
<CONFIG>
|
||||
<Package Version="3">
|
||||
<Name Value="MultiThreadProcsLaz"/>
|
||||
<CompilerOptions>
|
||||
<Version Value="8"/>
|
||||
<SearchPaths>
|
||||
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
|
||||
</SearchPaths>
|
||||
<Other>
|
||||
<CompilerPath Value="$(CompPath)"/>
|
||||
</Other>
|
||||
</CompilerOptions>
|
||||
<Files Count="3">
|
||||
<Item1>
|
||||
<Filename Value="mtprocs.pas"/>
|
||||
<UnitName Value="mtprocs"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<Filename Value="mtputils.pas"/>
|
||||
<UnitName Value="mtputils"/>
|
||||
</Item2>
|
||||
<Item3>
|
||||
<Filename Value="mtpcpu.pas"/>
|
||||
<UnitName Value="mtpcpu"/>
|
||||
</Item3>
|
||||
</Files>
|
||||
<Type Value="RunAndDesignTime"/>
|
||||
<RequiredPkgs Count="1">
|
||||
<Item1>
|
||||
<PackageName Value="FCL"/>
|
||||
<MinVersion Major="1" Valid="True"/>
|
||||
</Item1>
|
||||
</RequiredPkgs>
|
||||
<UsageOptions>
|
||||
<UnitPath Value="$(PkgOutDir)"/>
|
||||
</UsageOptions>
|
||||
<PublishOptions>
|
||||
<Version Value="2"/>
|
||||
<IgnoreBinaries Value="False"/>
|
||||
</PublishOptions>
|
||||
</Package>
|
||||
</CONFIG>
|
20
components/multithreadprocs/multithreadprocslaz.pas
Normal file
20
components/multithreadprocs/multithreadprocslaz.pas
Normal file
@@ -0,0 +1,20 @@
|
||||
{ This file was automatically created by Lazarus. do not edit!
|
||||
This source is only used to compile and install the package.
|
||||
}
|
||||
|
||||
unit MultiThreadProcsLaz;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
MTProcs, MTPUtils, MTPCPU, LazarusPackageIntf;
|
||||
|
||||
implementation
|
||||
|
||||
procedure Register;
|
||||
begin
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterPackage('MultiThreadProcsLaz', @Register);
|
||||
end.
|
Reference in New Issue
Block a user