Last Updated:

Multithreading in Delphi

Multithreading in Delphi

When you need to perform background operations or any processing that isn't strictly related to the UI, you can follow the technically most appropriate approach—create a separate thread of execution in the process.

Multithreaded programming may seem like a little-known topic, but it's actually not that difficult, even if you have to consider it with caution. It is worth knowing at least the basics of multithreading, because in the world of sockets and Internet programming, there is little that can be done without threads.

The RTLDelphi library provides the TThread class, which allows you to create and manipulate streams. You'll never use the TThread class directly because it's an abstract class—a class with a virtual abstract method. To use threads, you always use the TThread subclass and its functions.

The TThread class has a constructor with a single parameter (CreateSuspended) that allows you to choose whether to start the thread immediately or pause it until a later time. If a thread object starts automatically or when it resumes, it will run its Execute method until it is complete. The class provides a secure interface that includes two key methods for stream subclasses:

perform the procedure; virtual; abstract;

synchronization of procedures(Method: TThreadMethod);

The Execute method, declared as a virtual abstract procedure, must be overridden by each thread class. It contains the main code that you usually put into a flow function when using system functions.

The synchronization method is used to avoid simultaneous access to VCL components. The VCL code runs inside the main thread of the program, and you need to synchronize access to the VCL to avoid problems with re-input (errors when re-entering the function before the previous call is completed) and simultaneous access to shared resources.

The only Synchronize parameter is a method that does not take any parameters, typically a method of the same thread class. Because you cannot pass parameters to this method, typically some values are stored in the data of the stream object in the Execute method and are used in synchronized methods.

Delphi 7 includes two new versions of Synchronize that allow you to synchronize a method with the main thread without calling it from a thread object. Both the new overloaded synchronization and StaticSynchronize are methods of the TThread class and require a stream as a parameter.

Another way to avoid conflicts is to use the synchronization methods offered by the operating system. The SyncObjs block defines several VCL classes for some of these low-level synchronization objects, such as events (with the TEvent class and the TSingleEvent class) and critical sections (with the TCriticalSection class). (Synchronization events should not be confused with Delphi events because the two concepts are not related.)

A thread is a relatively independent planned unit of execution in a process. An application can have a main thread, a main thread can have multiple subthreads, subthreads can also have their own subthreadeds, which makes up a multithreaded application.

Because multiple threads often access the same memory area at the same time, frequent access to this area increases the likelihood of thread conflicts. As soon as a conflict occurs, it will lead to unpredictable results (the value of the total area is unpredictable), which indicates the need to synchronize processing flows.

All the code in this article is described using DELPHI, debug environment-Windowsme, Delphi 6. Windows API features that can be described in detail in the MSDN documentation.

First, here is an example that will lead us to the next discussion. This example does not take any steps to avoid thread conflicts. Its main process is as follows: the main thread starts two threads to frequently read and write the variables of the global variable, and then changes the results displayed in the list. Because the two threads are not synchronized, the thread produces unpredictable results when the letters change.

The letters in each line of the list must be the same, but the lines above are different. This is the result of thread conflicts. When two threads access shared memory at the same time, one thread did not change the memory and the other thread changed the memory because the process of writing the value is not serialized, resulting in an invalid result. This shows the importance of stream synchronization.

Here's the code:

unit.pasfile for this example
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

Define the window
class type
TForm1 = class (TForm)
ListBox1: TListBox;
ListBox2: TListBox;
Button1: TButton;
procedure Button1Click (Sender: TObject );
private
{Private declarations}
public
{Public declarations}
end;

Define the stream
class type
TListThread = class (TThread)
private
Str: String;
protected
procedureAddToList;//Add Str to the ListBox component
Procedure Execute; override;
public
LBox: TListBox;
end;
//Define variables
var
Form1: TForm1;
Letters: String = ‘AAAAAAAAAAAAAAAAAAAA’;//Global variable

implementation

{$ R * .dfm}

Part of the implementation of the procedureTListThread.Execute stream
class;
var
I, J, K: Integer;
begin
for i: = 0 to 50 do
begin
for J: = 1 to 20 do
for K: = 1 to 1000 do//Cycle 1000 times to increase the probability of conflict
if letters [j] <'Z 'the then
Letters [J]: = succ (Letters [J])
the else
Letters [J]: =' A ';
STR: = Letters;
synchronize (AddToList);//synchronous access VCL visual component
end;
end;

procedureTListThread.AddToList;
begin
LBox.Items.Add (str);//Add str to the list box
end;

Window class implementation part
procedure TForm1.Button1Click (Sender: TObject);
var
th1, th2: TListThread ;
begin
Listbox1.Clear;
Listbox2.Clear;
th1: = tlistThread.Create (true);//Create Thread 1
th2: = tlistThread.Create (true);//Create Thread 2
th1. LBox: = listBox1;
th2. LBox : = listBox2;
th1. Resume;//Start execution of
th2. Resume;
end;

From the example above, you can see that when multiple threads change a common variable at the same time, conflicts arise, so we should try to prevent them so that the multithreaded application we are developing can run stably. Let's improve it below. First, we use the critical partition for serialization to achieve synchronization.

Add a SyncObjs block to the uses section of the unit1.pas code in the example above, add the critical section global variable (TRTLCriticalSection) Critical1, add the InitializeCriticalSection (Critical1) code to the FormCreate event, and add the DeleteCriticalSection (Critical1) code to the FormDestroy event, and then modify the TListThread.

procedureTListThread.Execute;
var
I, J, K: Integer;
begin
for i: = 0 to 50 do
begin
? EnterCriticalSection (Critical1);//Enter critical section
for J: = 1 to 20 do
for K: = 1 to 3000 do
if letters [j] <'Z' then
letters [j]: = succ (Letters [j])
else
letters [j]: = 'A';
str: = letters;
? LeaveCriticalSection (Critical1);//Exit the critical synchronize (addtolist) section
;
end;
end;

Of course, we can also use other synchronization technologies, such as mutexe, semaphore, etc. These technologies are directly provided to us by Windows through the API.

The following is a brief description of several thread synchronization methods commonly used in Windows.

  1. Critical sections (critical sections), if there are sections in the source code that cannot be executed by two or more threads at the same time, you can use the critical sections to serialize the execution of that section of code. It is used only in a single process or a separate application program. The method of application is as follows:
    InitializeCriticalSection (Critical1) in the creation of the form// DeleteCriticalSection (Critical1) in the destruction of the form // EnterCriticalSection (Critical1) in the thread ...... protected code LeaveCriticalSection (Critical1)
  2. Mutex (mutually exclusive object) is a global object used for sequential access to resources. First we set up mutex, then we get access to resources and finally we release mutex. When installing a mutex, if another thread (or process) tries to install the same mutex, the thread will stop until the previous thread (or process) releases the mutex. Note that this may be a different sharing of application programs. The method of application is as follows:
    intheformcreation hMutex: = CreateMutex (nil, false, nil) //in the form destruction CloseHandle (hMutex) //in the thread WaitForSingleObject (hMutex, INFINITE) ...... protected code ReleaseMutex (hMutex)
  3. Semaphore, which is similar to mutex, but it can count. For example, you can allow access to a given resource to three threads at the same time. In fact, Mutex is the semaphore with the maximum number of units. The method of application is as follows:
    Intheformcreation hSemaphore: = CreateSemaphore (nil, lInitialCount, lMaximumCount, lpName) //In the form destruction CloseHandle (hSemaphore) // WaitForSingleObject (hSemaphore, INFINITE) ... protected code ReleaseSemaphore (hSemaphore, lReleaseCount, lpPreviousCount)
  4. You can also use the VCLTCriticalSection object in Delphi, which is defined in Syncobjs.pas.

When you develop multithreaded applications and have multiple threads accessing a shared resource or data at the same time, you should consider the problem of thread synchronization.