Difference between revisions of "Example of multi-threaded application: array of threads"

From Free Pascal wiki
Jump to navigationJump to search
Line 34: Line 34:
  
 
== 2. Add threads class. ==
 
== 2. Add threads class. ==
I also use a separate unit for it. So I need to describe 'property Terminated' as the public (to use it from the main program).
+
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.
 
 
<syntaxhighlight>unit MyThreads;
 
  
 +
<syntaxhighlight>unit mythreads;
 
{$mode objfpc}{$H+}
 
{$mode objfpc}{$H+}
 
 
interface
 
interface
 
 
uses
 
uses
   Classes, SysUtils;  
+
   Classes, SysUtils, Math;
 
+
type
 +
  TData = array of double;
 +
  PData = ^TData;
 
  Type
 
  Type
 
     TMyThread = class(TThread)
 
     TMyThread = class(TThread)
 
     private
 
     private
 
     protected
 
     protected
 +
      tPtr: PData;
 +
      tstart,tfinish: integer;
 
       procedure Execute; override;
 
       procedure Execute; override;
 
     public
 
     public
      start,finish: integer;  //I will initialise these fields before running the thread. So each thread will receive a part of task.
 
 
       property Terminated;
 
       property Terminated;
       Constructor Create(CreateSuspended : boolean);
+
       Constructor Create(lstart, lfinish: integer; var lPtr: PData);
 
     end;
 
     end;
  
 
implementation
 
implementation
  
constructor TMyThread.Create(CreateSuspended : boolean);
+
  constructor TMyThread.Create(lstart, lfinish: integer; var lPtr: PData);
 
   begin
 
   begin
     FreeOnTerminate := True;
+
     FreeOnTerminate := False;
     inherited Create(CreateSuspended);
+
    tstart := lstart;
 +
    tfinish := lfinish;
 +
    tPtr := lPtr;
 +
     inherited Create(false);
 
   end;
 
   end;
 
 
   procedure TMyThread.Execute;
 
   procedure TMyThread.Execute;
 
   var
 
   var
 
     i: integer;
 
     i: integer;
 
   begin
 
   begin
  i:=start;
+
    for i := tstart to tfinish do
    while (not Terminated) and (i<=finish) do
+
        tPtr^[i] := power(i,0.5);
      begin
 
          MyProcedure(results[i]);
 
          inc(i);
 
          if i=finish+1 then Terminate;
 
      end;
 
 
   end;
 
   end;
end.</syntaxhighlight>
+
 
 +
end.
 +
</syntaxhighlight>
  
 
== 3. Rewrite main program. ==
 
== 3. Rewrite main program. ==

Revision as of 13:53, 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!

Tips: in my Lazarus IDE I was not able to debug multi-threading applications if I don't use 'pthreads'. I have red 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  cthreads,
//    cmem,
//    pthreads,
      Classes, SysUtils, CustApp, MyThreads, Global

var
 ThreadArray: array [1..100] of TMyThread; //We will create no more than 100 threads.
 i, number_of_threads: integer;
begin

number_of_threads:=100;

while n div number_of_threads <1 do dec(number_of_threads); //If n<100 then we don't have enough tasks for 100 threads.

for i:=1 to number_of_threads do begin
ThreadArray[i]:= TMyThread.Create(True);
ThreadArray[i].start:=(i-1)*(n div number_of_threads)+1;;
ThreadArray[i].finish:=i*(n div number_of_threads);
ThreadArray[i].Resume;
end;

for i:=1 to 10 do if not ThreadArray[i].Terminated then Sleep(10); //Waiting while threads will not finish their job.

for i:=1 to number_of_threads*(n div number_of_threads) do Writeln(file,results[i]); //Writing results in order.
                                                                                     //Or You can calculate a sum here and write only it.

//Now we should finish part of the task that couldn't be divided to 100 threads.
//For examle, if n=10050 then 10000 will be divided to 100 threads. 
//And last 50 (< 100) we should finish in non-parallel mode.
//That's how I do this. 
//Instead, You can write something like: 'if i=number_of_threads then ThreadArray[i].finish:=n;' in the loop above.
//In this case last thread will finish the job.

if n mod number_of_threads<>0 then
for i:=(n div number_of_threads) + 1 to n do begin
                                             MyProcedure(result);
                                             Writeln(File,result);
                                             end;

Terminate;
end;