Difference between revisions of "Example of multi-threaded application: array of threads"
Line 81: | Line 81: | ||
You need to add 'cthreads' to the main unit, not to unit with threads! | You need to add 'cthreads' to the main unit, not to unit with threads! | ||
− | + | Note that there are two ways to determine whether all the threads have completed. You can use the in-built "waitFor" function - this works very nicely but on my OSX computer I noted that it refreshes only every 100ms. This is perfect for real world programs (we only use threading for computationally slow problems) and reduces thread overhead. However, for quick example benchmarks it can hide the benefit of threading (as operations require a minimum of 100ms regardless of the number of threads). Therefore, in this example I check the thread' terminated status and only sleep 2ms between checking. This provides more accurate bench mark timing. | |
− | + | Remember to free each thread when you are done with it. Since we set "FreeOnTerminate := False" the program needs to do this explicitly. | |
− | |||
− | |||
− | |||
+ | Tips: in my Lazarus IDE I was not able to debug multi-threading applications if I don't use 'pthreads'. I have read that if you use 'cmem', the program works faster, but I strongly recommend you to check it for any particular case (my program hangs when I use 'cmem'). | ||
+ | |||
+ | <syntaxhighlight>uses // cmem,pthreads, | ||
+ | cthreads, Classes, SysUtils, CustApp, MyThreads; | ||
+ | |||
+ | procedure DoThreading (nThreadsIn, nValues: integer); | ||
var | var | ||
− | + | threadArray: array of TMyThread; | |
− | i, | + | dataArray: TData; |
+ | lData : PData; | ||
+ | nThreads, i,lStart,lFinish: integer; | ||
+ | StartMS: double; | ||
begin | begin | ||
− | + | if (nThreadsIn < 1) or (nValues < 1) then exit; | |
− | + | nThreads := nThreadsIn; | |
− | + | if nThreads > nValues then nThreads := nValues; | |
− | + | StartMS:=timestamptomsecs(datetimetotimestamp(now)); | |
− | + | setlength(threadArray,nThreads+1);//+1 since indexed 0..n-1 | |
− | for i:=1 to | + | setlength(dataArray, nValues+1);//+1 since indexed 0..n-1 |
− | + | lData := @dataArray; | |
− | + | lStart := 1; | |
− | ThreadArray[i]. | + | for i:=1 to nThreads do begin |
− | + | if i < nThreads then | |
+ | lFinish:=i*(nValues div nThreads) | ||
+ | else | ||
+ | lFinish:= nValues; | ||
+ | threadArray[i]:= TMyThread.Create(lStart, lFinish, lData); | ||
+ | lStart := lFinish+1; | ||
+ | end; | ||
+ | for i:=1 to nThreads do if not ThreadArray[i].Terminated then Sleep(2); | ||
+ | //for i:=1 to nThreads do threadArray[i].waitFor; //appears to sleep for 100ms on OSX | ||
+ | for i:=1 to nThreads do threadArray[i].Free; | ||
+ | Writeln(inttostr(nThreads)+' Threads processed '+inttostr(nValues)+' values in '+floattostr(timestamptomsecs(datetimetotimestamp(now))-StartMS)+'ms, with '+inttostr(nValues)+'^0.5 = '+floattostr(dataArray[nValues])); | ||
end; | end; | ||
− | + | begin | |
− | + | DoThreading(1,10); | |
− | + | DoThreading(2,10); | |
− | + | DoThreading(4,10); | |
− | + | DoThreading(1,100000); | |
− | + | DoThreading(2,100000); | |
− | + | DoThreading(4,100000); | |
− | + | end. </syntaxhighlight> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
[[Category:Tutorials]] | [[Category:Tutorials]] | ||
[[Category:Parallel programming]] | [[Category:Parallel programming]] | ||
[[Category:Multitasking]] | [[Category:Multitasking]] |
Revision as of 14:00, 16 June 2013
Here I want to show an example how to create a lot of threads and wait while they will not finish their jobs (I don't need any synchronisation). I'm writing this tutorial because it was not obvious for me to write such a program after reading Multithreaded Application Tutorial. I was writing my application for Linux x86_64.
Let's assume that we have the following loop:
var results: integer;
File: text;
begin
...
for i:=1 to n do begin
MyProcedure(result);
Writeln(File,result);
end;
...
This loop runs procedure that "calculate" some 'result'. After this we write this array to a text file... Now we want to divide this job to many parallel threads.
To do this, You need to perform the following steps:
1. Manage memory.
Your threads can read any variables any time You need, but they shouldn't write any data to same variables (global) at the same time. So Your threads will not be able to write anything to one variable like 'result'. And they will not be able to write it to file. Moreover, I was interested to write my results to file in order (from 1 to n), and threads will not do this. So first step is to create global array of results (1 cell for 1 thread). If your procedure write something to many variables - you can create several arrays or array of record.
I use a separate unit for global variables:
unit Global;
{$mode objfpc}{$H+}
interface
uses Classes, SysUtils;
var results: array [1..100] of threads; //We will create no more than 100 threads.
implementation
end.
2. Add threads class.
I also use a separate unit for defining the behavior of the threads. Note that I am setting the "FreeOnTerminate" to false - so my program will need to dispose of each thread when it is done. This makes it easier to juggle multiple threads (if you set FreeOnTerminate to true and launch multiple very fast jobs it is possible that the thread will be released before your program checks whether the thread is completed - and checking a non-existent thread can cause an exception). By setting FreeOnTerminate to false I can ensure that each thread completed successfully.
unit mythreads;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Math;
type
TData = array of double;
PData = ^TData;
Type
TMyThread = class(TThread)
private
protected
tPtr: PData;
tstart,tfinish: integer;
procedure Execute; override;
public
property Terminated;
Constructor Create(lstart, lfinish: integer; var lPtr: PData);
end;
implementation
constructor TMyThread.Create(lstart, lfinish: integer; var lPtr: PData);
begin
FreeOnTerminate := False;
tstart := lstart;
tfinish := lfinish;
tPtr := lPtr;
inherited Create(false);
end;
procedure TMyThread.Execute;
var
i: integer;
begin
for i := tstart to tfinish do
tPtr^[i] := power(i,0.5);
end;
end.
3. Rewrite main program.
You need to add 'cthreads' to the main unit, not to unit with threads!
Note that there are two ways to determine whether all the threads have completed. You can use the in-built "waitFor" function - this works very nicely but on my OSX computer I noted that it refreshes only every 100ms. This is perfect for real world programs (we only use threading for computationally slow problems) and reduces thread overhead. However, for quick example benchmarks it can hide the benefit of threading (as operations require a minimum of 100ms regardless of the number of threads). Therefore, in this example I check the thread' terminated status and only sleep 2ms between checking. This provides more accurate bench mark timing.
Remember to free each thread when you are done with it. Since we set "FreeOnTerminate := False" the program needs to do this explicitly.
Tips: in my Lazarus IDE I was not able to debug multi-threading applications if I don't use 'pthreads'. I have read that if you use 'cmem', the program works faster, but I strongly recommend you to check it for any particular case (my program hangs when I use 'cmem').
uses // cmem,pthreads,
cthreads, Classes, SysUtils, CustApp, MyThreads;
procedure DoThreading (nThreadsIn, nValues: integer);
var
threadArray: array of TMyThread;
dataArray: TData;
lData : PData;
nThreads, i,lStart,lFinish: integer;
StartMS: double;
begin
if (nThreadsIn < 1) or (nValues < 1) then exit;
nThreads := nThreadsIn;
if nThreads > nValues then nThreads := nValues;
StartMS:=timestamptomsecs(datetimetotimestamp(now));
setlength(threadArray,nThreads+1);//+1 since indexed 0..n-1
setlength(dataArray, nValues+1);//+1 since indexed 0..n-1
lData := @dataArray;
lStart := 1;
for i:=1 to nThreads do begin
if i < nThreads then
lFinish:=i*(nValues div nThreads)
else
lFinish:= nValues;
threadArray[i]:= TMyThread.Create(lStart, lFinish, lData);
lStart := lFinish+1;
end;
for i:=1 to nThreads do if not ThreadArray[i].Terminated then Sleep(2);
//for i:=1 to nThreads do threadArray[i].waitFor; //appears to sleep for 100ms on OSX
for i:=1 to nThreads do threadArray[i].Free;
Writeln(inttostr(nThreads)+' Threads processed '+inttostr(nValues)+' values in '+floattostr(timestamptomsecs(datetimetotimestamp(now))-StartMS)+'ms, with '+inttostr(nValues)+'^0.5 = '+floattostr(dataArray[nValues]));
end;
begin
DoThreading(1,10);
DoThreading(2,10);
DoThreading(4,10);
DoThreading(1,100000);
DoThreading(2,100000);
DoThreading(4,100000);
end.