Difference between revisions of "Multithreaded Application Tutorial"
(Correct typing) |
|||
Line 1: | Line 1: | ||
== Overview == | == Overview == | ||
− | In this page I will try to explain how to write and debug a | + | In this page I 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 | + | A multithreaded application is one that creates two or more threads of execution that work at the same time. |
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. | 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 | + | 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 ( | + | 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 | + | 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, is to create a server application that is able to respond to many clients at the same time. | |
== The TThread Class == | == The TThread Class == | ||
To create a multithreaded application, the easiest way is to use the TThread Class. | To create a multithreaded application, the easiest way is to use the TThread Class. | ||
− | This class permits the creation of | + | 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 | + | 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. | + | 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. | 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. | As guessed, being Suspended = True will prevent the thread to start automatically after the creation. | ||
Line 26: | Line 26: | ||
If the thread is created suspended, then it will run only after the Resume method is called. | 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. | + | 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. | 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. | |
− | stack size is used. | ||
In the overrided Execute method you will write the code that will run on the thread. | In the overrided Execute method you will write the code that will run on the thread. | ||
Line 36: | Line 35: | ||
Terminated : boolean; | Terminated : boolean; | ||
− | If the thread has | + | 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. | |
− | |||
− | |||
− | |||
− | There is another important property of TThread: FreeOnTerminate. If this property is true, the thread object is automatically | + | 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. | ||
+ | |||
+ | 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: | Example: | ||
Line 118: | Line 119: | ||
== Units needed for multithreaded application == | == Units needed for multithreaded application == | ||
On Windows, you don´t need any special unit for this to work. | On Windows, you don´t need any special unit for this to work. | ||
− | On Linux, you need the cthreads unit | + | On Linux, you need the cthreads unit and it ''must'' be the first used unit on the project (the program unit)! |
So, your Lazarus application code should look like: | So, your Lazarus application code should look like: | ||
− | program | + | program MyMultiThreadedProgram; |
{$H+} | {$H+} | ||
uses | uses | ||
Line 137: | Line 138: | ||
== Debuging Multithreaded Applications with Lazarus == | == Debuging Multithreaded Applications with Lazarus == | ||
− | The | + | The debugging on Lazarus is not fully functional yet. But, if you try to debug a multithreaded application on Linux, you will have one big problem: the X server will hang. |
You will ask: How to solve it? | You will ask: How to solve it? |
Revision as of 10:22, 7 November 2005
Overview
In this page I 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.
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 to start 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.
The TThread Class
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.
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; public Constructor Create(Suspended : boolean); override; procedure Execute; override; end;
constructor TMyThread.Create(Suspended : boolean); begin inherited Create(Suspended); FreeOnTerminate := True; end;
procedure TMyThread.ShowStatus; begin Form1.Caption := fStatusText; end; procedure TMyThread.Execute; var newStatus : string; begin fStatusText := 'Starting...'; Syncronize(Showstatus); fStatusText := 'Running...'; while (not Terminated) and ([any condition required]) do begin ... [here goes the code of the main thread loop] ... if NewStatus <> fStatus then begin fStatusText = newStatus; Syncronize(Showstatus); end; end; end;
On the application,
var MyThread : TMyThread; begin MyThread := TMyThread.Create(True); // This way it starts automatically ... [Here the code initialices anythig required before the threads starts executing] ... MyThread.Resume; 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, you need the cthreads unit and it must be the first used unit on the project (the program unit)!
So, your Lazarus application code should look like:
program MyMultiThreadedProgram; {$H+} uses {$ifdef linux} cthreads, {$endif} Interfaces, // this includes the LCL widgetset Forms { add your units here },
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. But, if you try to debug a multithreaded application on Linux, you will have one big problem: the X server will hang.
You will ask: How to solve it? I will answer: I DON´T KNOW... but I will give you a work around:
You could 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.