Example of multi-threaded application: array of threads

From Lazarus wiki
Revision as of 12:53, 16 June 2013 by Crorden (talk | contribs) (2. Add threads class.)

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;