Difference between revisions of "Multithreaded Application Tutorial"

From Free Pascal wiki
Jump to navigationJump to search
Line 13: Line 13:
 
The main idea is that the application can do some processing in background (in a second thread) while the user can keep working (using the main thread).
 
The main idea is that the application can do some processing in background (in a second thread) while the user can keep working (using the main thread).
  
Another use of threads is just to have a better responding application. If you create an application, and when the user press a button, the application start processing (a big job)... and while processing, the screen stop responding, and gives the user the sensation that the application is dead, that is not so nice. If the big job runs in a second thread, the application keeps responding (almost) as if it were idle. In this case it is a good idea, before starting the thread, to disable the buttons of the form to avoid the user to start more than one thread for the job.
+
Another use of threads is just to have a better responding application. If you create an application, and when the user press a button, the application start processing (a big job)... and while processing, the screen stop responding, and gives the user the sensation that the application is dead, that is not so nice. If the big job runs in a second thread, the application keeps responding (almost) as if it were idle. In this case it is a good idea, before starting the thread, to disable the buttons of the form to avoid the user starting more than one thread for the job.
  
 
Another use, is to create a server application that is able to respond to many clients at the same time.
 
Another use, is to create a server application that is able to respond to many clients at the same time.

Revision as of 13:07, 29 April 2007

Deutsch (de) English (en) español (es) français (fr) 日本語 (ja) polski (pl) português (pt) русский (ru) slovenčina (sk) 中文(中国大陆)‎ (zh_CN)

Overview

This page will try to explain how to write and debug a multithreaded application with Free Pascal and Lazarus.

A multithreaded application is one that creates two or more threads of execution that work at the same time.

If you are a new to multithreading, please read the paragraph "Do you need multithreading?" to find out, if you really need it. You can save yourself a lot of headaches.

One of the threads is called the Main Thread. The Main Thread is the one that is created by the Operating System, once our application starts. The Main Thread must be the only thread that updates the components that interfaces with the user (else, the application may hang).

The main idea is that the application can do some processing in background (in a second thread) while the user can keep working (using the main thread).

Another use of threads is just to have a better responding application. If you create an application, and when the user press a button, the application start processing (a big job)... and while processing, the screen stop responding, and gives the user the sensation that the application is dead, that is not so nice. If the big job runs in a second thread, the application keeps responding (almost) as if it were idle. In this case it is a good idea, before starting the thread, to disable the buttons of the form to avoid the user starting more than one thread for the job.

Another use, is to create a server application that is able to respond to many clients at the same time.

Do you need multithreading?

If you are a newbie to multithreading and you only want to make your application more responsive while your application computes big load of works, then multithreading might not be, what you are searching for. Multithreaded aplications are always harder to debug and they are often much more complex. And in many cases you don't need multithreading. A single thread is enough. If you can split up the time consuming task into several small chunks, then instead you should use Application.ProcessMessages. This method let the LCL handle all waiting messages and returns. The idea is to process a part of the work, then call Application.ProcessMessages to see if the User aborts or clicked somewhere or the process indicator repaints, then continue with the next part of the work, call Application.ProcessMessages and so forth.

For example: Reading a big file and process it. See examples/multithreading/singlethreadingexample1.lpi.

Multithreading is only needed for

  • blocking handles, like network communications
  • using multiple processors at once
  • algorithms and library calls, that can not be split up into small parts.

The TThread Class

The following example can be found in the examples/multithreading/ directory.

To create a multithreaded application, the easiest way is to use the TThread Class.

This class permits the creation of an additional thread (alongside the main thread) in a simple way.

Normally you only have to override 2 methods: the Create constructor, and the Execute method.

In the constructor, you will prepare the thread to run. You will set the initial values of the variables or properties you need. The original constructor of TThread requires a parameter called Suspended. As guessed, being Suspended = True will prevent the thread to start automatically after the creation. If Suspended = False, the thread will start running just after the creation. If the thread is created suspended, then it will run only after the Resume method is called.

As of FPC version 2.0.1 and later, TThread.Create also has an implicit parameter for Stack Size. You can now change the default stack size of each thread you create if you need it. Deep procedure call recursions in a thread are a good example. If you don't specify the stack size parameter, a default OS stack size is used.

In the overrided Execute method you will write the code that will run on the thread.

The TThread class has one important property: Terminated : boolean;

If the thread has a loop (and this is usual), the loop should be exited when Terminated is true (it is false by default). So in each cycle, it must check if Terminated is True, and if it is, must exit the .Execute method as quickly as possible, after any necessary cleanup.

So keep in mind that the Terminate method does not do anything by default: the .Execute method must explicitly implement support for it to quit it's job.

As we explained earlier, the thread should not interact with the visible components. To show something to the user it must do so in the main thread. To do this, a TThread method called Synchronize exists. Synchronize requires a method (that takes no parameters) as an argument. When you call that method through Synchronize(@MyMethod), the thread execution will be paused, the code of MyMethod will run in the main thread, and then the thread execution will be resumed. The exact working of Synchronize depends on the platform, but basically it does this: It posts a message onto the main message queue and goes to sleep. Eventually the main thread processes the message and calls MyMethod. This way MyMethod is called without context, that means not during a mouse down event or during paint event, but after. After the main thread executed MyMethod, it wakes the sleeping Thread and processes the next message. The Thread then continues.

There is another important property of TThread: FreeOnTerminate. If this property is true, the thread object is automatically freed when the thread execution (.Execute method) stops. Otherwise the application will need to free it manually.

Example:

 Type
   TMyThread = class(TThread)
   private
     fStatusText : string;
     procedure ShowStatus;
   protected
     procedure Execute; override;
   public
     Constructor Create(CreateSuspended : boolean);
   end;
 constructor TMyThread.Create(CreateSuspended : boolean);
 begin
   FreeOnTerminate := True;
   inherited Create(CreateSuspended);
 end;
 procedure TMyThread.ShowStatus;
 // this method is executed by the mainthread and can therefore access all GUI elements.
 begin
   Form1.Caption := fStatusText;
 end;

 procedure TMyThread.Execute;
 var
   newStatus : string;
 begin
   fStatusText := 'TMyThread Starting...';
   Synchronize(@Showstatus);
   fStatusText := 'TMyThread Running...';
   while (not Terminated) and ([any condition required]) do
     begin
       ...
       [here goes the code of the main thread loop]
       ...
       if NewStatus <> fStatusText then
         begin
           fStatusText := newStatus;
           Synchronize(@Showstatus);
         end;
     end;
 end;

On the application,

 var
   MyThread : TMyThread;
 begin
   MyThread := TMyThread.Create(True); // This way it doesn't start automatically
   ...
   [Here the code initialises anything required before the threads starts executing]
   ...
   MyThread.Resume;
 end;

If you want to make your application more flexible you can create an event for the thread - this way your synchronized method won't be tightly coupled with a specific form or class - you can attach listeners to the thread's event. Here is an example:


 Type
   TShowStatusEvent = procedure(Status: String) of Object;
   TMyThread = class(TThread)
   private
     fStatusText : string;
     FOnShowStatus: TShowStatusEvent;
     procedure ShowStatus;
   protected
     procedure Execute; override;
   public
     Constructor Create(CreateSuspended : boolean);
     property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
   end;
 constructor TMyThread.Create(CreateSuspended : boolean);
 begin
   FreeOnTerminate := True;
   inherited Create(CreateSuspended);
 end;
 procedure TMyThread.ShowStatus;
 // this method is executed by the mainthread and can therefore access all GUI elements.
 begin
   if Assigned(FOnShowStatus) then
   begin
     FOnShowStatus(fStatusText);
   end;
 end;
 procedure TMyThread.Execute;
 var
   newStatus : string;
 begin
   fStatusText := 'TMyThread Starting...';
   Synchronize(@Showstatus);
   fStatusText := 'TMyThread Running...';
   while (not Terminated) and ([any condition required]) do
     begin
       ...
       [here goes the code of the main thread loop]
       ...
       if NewStatus <> fStatusText then
         begin
           fStatusText := newStatus;
           Synchronize(@Showstatus);
         end;
     end;
 end;

On the application,

 Type
   TForm1 = class(TForm)
     Button1: TButton;
     Label1: TLabel;
     procedure FormCreate(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
   private
     { private declarations }
     MyThread: TMyThread; 
     procedure ShowStatus(Status: string);
   public
     { public declarations }
   end;
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   inherited;
   MyThread := TMyThread.Create(true);
   MyThread.OnShowStatus := @ShowStatus;
 end;
 procedure TForm1.FormDestroy(Sender: TObject);
 begin
   MyThread.Terminate;
   MyThread.Free;
   inherited;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
  MyThread.Resume;
 end;
 procedure TForm1.ShowStatus(Status: string);
 begin
   Label1.Caption := Status;
 end;

Special things to take care of

There is a potential headache in Windows with Threads if you use the -Ct (stack check) switch. For reasons not so clear the stack check will "trigger" on any TThread.Create if you use the default stack size. The only work-around for the moment is to simply not use -Ct switch. Note that it does NOT cause an exception in the main thread, but in the newly created one. This "looks" like if the thread was never started.

A good code to check for this and other exceptions which can occur in thread creation is:


    MyThread:=TThread.Create(False);
    if Assigned(MyThread.FatalException) then
      raise MyThread.FatalException;


This code will asure that any exception which occured during thread creation will be raised in your main thread.

Units needed for multithreaded application

On Windows, you don´t need any special unit for this to work. On Linux, MacOSX and FreeBSD, you need the cthreads unit and it must be the first used unit of the project (the program unit, .lpr)!

So, your Lazarus application code should look like:

 program MyMultiThreadedProgram;
 {$mode objfpc}{$H+}
 uses
 {$ifdef unix}
   cthreads,
 {$endif}
   Interfaces, // this includes the LCL widgetset
   Forms
   { add your units here },

If you forget this you will get this error on startup:

 This binary has no thread support compiled in.
 Recompile the application with a thread-driver in the program uses clause before other units using thread.

SMP Support

The good news is that if your application works properly multithreaded this way, it is already SMP enabled!

Debuging Multithreaded Applications with Lazarus

The debugging on Lazarus is not fully functional yet.

Debugging output

In a single threaded application, you can simply write to console/terminal/whatever and the order of the lines is the same as they were written. In multithreaded application things are more complicated. If two threads are writing, say a line is written by thread A before a line by thread B, then the lines are not neccessarily written in that order. It can even happen, that a thread writes its output, while the other thread is writing a line.

The LCLProc unit contains several functions, to let each thread write to its own log file:

 procedure DbgOutThreadLog(const Msg: string); overload;
 procedure DebuglnThreadLog(const Msg: string); overload;
 procedure DebuglnThreadLog(Args: array of const); overload;
 procedure DebuglnThreadLog; overload;

For example: Instead of writeln('Some text ',123); use

 DebuglnThreadLog(['Some text ',123]);

This will append a line 'Some text 123' to Log<PID>.txt, where <PID> is the process ID of the current thread.

It is a good idea to remove the log files before each run:

 rm -f Log* && ./project1

Linux

If you try to debug a multithreaded application on Linux, you will have one big problem: the X server will hang.

It is unknown how to solve this properly, but a workaround is:

Create a new instance of X with:

 X :1 &

It will open, and when you switch to another desktop (the one you are working with pressing CTRL+ALT+F7), you will be able to go back to the new graphical desktop with CTRL+ALT+F8 (if this combination does not work, try with CTRL+ALT+F2... this one worked on Slackware).

Then you could, if you want, create a desktop session on the X started with:

 gnome-session --display=:1 &

Then, in Lazarus, on the run parameters dialog for the project, check "Use display" and enter :1.

Now the application will run on the seccond X server and you will be able to debug it on the first one.

This was tested with Free Pascal 2.0 and Lazarus 0.9.10 on Windows and Linux.

Widgetsets

The win32, the gtk and the carbon interfaces fully support multithreading. This means, TThread, critical sections and Synchronize work.

Critical sections

A critical section is an object used to make sure, that some part of the code is executed only by one thread at a time. A critical section needs to be created/initialized before it can be used and be freed when it is not needed anymore.

Critical sections are normally used this way:

Add the unit SyncObjs.

Declare the section (globally for all threads which should access the section):

 MyCriticalSection: TRTLCriticalSection;

Create the section:

 InitializeCriticalSection(MyCriticalSection);

Run some threads. Doing something exclusively

 EnterCriticalSection(MyCriticalSection);
 try
   // access some variables, write files, send some network packets, etc
 finally
   LeaveCriticalSection(MyCriticalSection);
 end;

After all threads terminated, free it:

 DeleteCriticalSection(MyCriticalSection);

As an alternative, you can use a TCriticalSection object. The creation does the initialization, the Enter method does the EnterCriticalSection, the Leave method does the LeaveCriticalSection and the destruction of the object does the deletion.

For example: 5 threads incrementing a counter. See lazarus/examples/multithreading/criticalsectionexample1.lpi

BEWARE: There are two sets of the above 4 functions. The RTL and the LCL ones. The LCL ones are defined in the unit LCLIntf and LCLType. Both work pretty much the same. You can use both at the same time in your application, but you should not use a RTL function with an LCL Critical Section and vice versus.


Sharing Variables

If some threads share a variable, that is read only, then there is nothing to worry about. Just read it. But if one or several threads changes the variable, then you must make sure, that only one thread accesses the variables at a time.

For example: 5 threads incrementing a counter. See lazarus/examples/multithreading/criticalsectionexample1.lpi

Waiting for another thread

If a thread A needs a result of another thread B, it must wait, till B has finished.

Important: The main thread should never wait for another thread. Instead use Synchronize (see above).

See for an example: lazarus/examples/multithreading/waitforexample1.lpi

{ TThreadA }

procedure TThreadA.Execute;
begin
  Form1.ThreadB:=TThreadB.Create(false);
  // create event
  WaitForB:=RTLEventCreate;
  while not Application.Terminated do begin
    // wait infinitely (until B wakes A)
    RtlEventWaitFor(WaitForB);
    writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
  end;
end;

{ TThreadB }

procedure TThreadB.Execute;
var
  i: Integer;
begin
  Counter:=0;
  while not Application.Terminated do begin
    // B: Working ...
    Sleep(1500);
    inc(Counter);
    // wake A
    RtlEventSetEvent(Form1.ThreadA.WaitForB);
  end;
end;