Thread class in Delphi--(1)
Thread class in Delphi--(1) Raptor (original work)
Keyword ThreadEventCriticalSectionSynchronize
Thread classes in Delphi
Raptors [MentalStudio]
http://mental.mentsu.com
(one)
There is a thread class TThread in Delphi that is used to implement multi-thread programming. Most Delphi books have mentioned this, but basically they give a brief introduction to several members of the TThread class, and then explain the Execute The implementation and usage of Synchronize are finished. However, this is not the whole of multi-threaded programming. The purpose of my writing is to add to this.
A thread is essentially a piece of code running concurrently in a process. A process has at least one thread, the so-called main thread. There can also be multiple child threads. When more than one thread is used in a process, it is called "multi-threading".
So how is this so-called "a piece of code" defined? It is actually a function or process (for Delphi).
If you use Windows API to create threads, it is implemented through an API function called CreateThread, which is defined as:
HANDLECreateThread(
LPSECURITY_ATTRIBUTESlpThreadAttributes,
DWorddwStackSize,
LPTHREAD_START_ROUTINElpStartAddress,
LPVOIDlpParameter,
DWORDdwCreationFlags,
LPDWORDlpThreadId
);
Their parameters are as mentioned in their names, namely: thread attributes (used to set thread security attributes under NT, invalid under 9X), stack size, starting address, parameters, and creation flags (used to set threads The status at creation time), thread ID, and finally return the thread Handle. The starting address is the entrance to the thread function, and the thread ends until the thread function ends.
The execution process of the entire thread is as follows:
Because CreateThread parameters are many and it is a Windows API, a general threading function is provided in CRuntimeLibrary (in theory, it can be used in any OS that supports threads):
unsignedlong_beginthread(void(_USERENTRY*__start)(void*),unsigned__stksize,void*__arg);
Delphi also provides a similar function with the same function:
functionBeginThread(SecurityAttributes:Pointer;StackSize:LongWord;ThreadFunc:TThreadFunc;Parameter:Pointer;CreationFlags:LongWord;varThreadId:LongWord):Integer;
The functions of these three functions are basically the same. They all put the code in the thread function into an independent thread to execute. The biggest difference between thread functions and general functions is that as soon as the thread function is started, these three threads start functions return, and the main thread continues to execute downward, while the thread function is executed in an independent thread. How long does it take to execute and what When returning, the main thread does not care about it or know it.
Under normal circumstances, after the thread function returns, the thread terminates. But there are other ways:
Windows API:
VOIDExitThread(DWORDdwExitCode);
CRuntimeLibrary:
void_endthread(void);
DelphiRuntimeLibrary:
PRocedureEndThread(ExitCode:Integer);
In order to record some necessary thread data (state/properties, etc.), the OS will create an internal Object for the thread. For example, the Handle in Windows is the Handle of this internal Object, so the Object should be released when the thread ends.
Although multi-threading programming can be easily performed using API or RTL (RuntimeLibrary), more detailed processing is still required. For this reason, Delphi has made a better package of threads in the Classes unit, which is VCL Thread class: TThread
It is also very simple to use this class. Most Delphi books say that the basic usage is: first derive a own thread class from TThread (because TThread is an abstract class and cannot generate instances), and then the Override abstract method: Execute( This is the thread function, which is the code part executed in the thread). If you need to use a visual VCL object, you also need to use the Synchronize process. For specific details about this, I will not repeat them here, please refer to the relevant books.
Next in this article, we will discuss how the TThread class encapsulates threads, that is, we will conduct in-depth research on the implementation of the TThread class. Because it is only better to use it if you really understand it.
Below is the declaration of the TThread class in DELPHI7 (this article only discusses the implementation under the Windows platform, so all the code about the linux platform part is removed):
TThread=class
Private
FHandle:THandle;
FThreadID:THandle;
FCreateSuspended:Boolean;
FTerminated:Boolean;
FSuspended:Boolean;
FFreeOnTerminate:Boolean;
FFinished:Boolean;
FReturnValue:Integer;
FOnTerminate:TNotifyEvent;
FSynchronize:TSynchronizeRecord;
FFatalException:TObject;
procedureCallOnTerminate;
classprocedureSynchronize(ASyncRec:PSynchronizeRecord); overload;
functionGetPriority:TThreadPriority;
procedureSetPriority(Value:TThreadPriority);
procedureSetSuspended(Value:Boolean);
protected
procedureCheckThreadError(ErrCode:Integer);overload;
procedureCheckThreadError(Success:Boolean);overload;
procedureDoTerminate;virtual;
procedureExecute;virtual;abstract;
procedureSynchronize(Method:TThreadMethod);overload;
propertyReturnValue:IntegerreadFReturnValuewriteFReturnValue;
propertyTerminated:BooleanreadFTerminated;
public
constructorCreate(CreateSuspended:Boolean);
destructorDestroy;override;
procedureAfterConstruction;override;
procedureResume;
procedureSuspend;
procedureTerminate;
functionWaitFor:LongWord;
classprocedureSynchronize(AThread:TThread;AMethod:TThreadMethod);overload;
classprocedureStaticSynchronize(AThread:TThread;AMethod:TThreadMethod);
propertyFatalException:TObjectreadFFatalException;
propertyFreeOnTerminate:BooleanreadFFreeOnTerminatewriteFFreeOnTerminate;
propertyHandle:THandlereadFHandle;
propertyPriority:TThreadPriorityreadGetPrioritywriteSetPriority;
propertySuspended:BooleanreadFSuspendedwriteSetSuspended;
propertyThreadID:THandlereadFThreadID;
propertyOnTerminate:TNotifyEventreadFOnTerminatewriteFOnTerminate;
end;
The TThread class is a relatively simple class in Delphi RTL, with not many class members and the class attributes are very simple and clear. This article will only analyze a few more important class member methods and the only event: OnTerminate.
(to be continued)
Thread class in Delphi--(2)
Thread class in Delphi--(2) Raptor (original work)
Keyword ThreadEventCriticalSectionSynchronize
Thread classes in Delphi
Raptors [MentalStudio]
http://mental.mentsu.com
Two
The first is the constructor:
constructorTThread.Create(CreateSuspended:Boolean);
Begin
inheritedCreate;
AddThread;
FSuspended:=CreateSuspended;
FCreateSuspended:=CreateSuspended;
FHandle:=BeginThread(nil,0,@ThreadProc,Pointer(Self),CREATE_SUSPENDED,FThreadID);
ifFHandle=0then
raiseEThread.CreateResFmt(@SThreadCreateError,[SysErrorMessage(GetLastError)]);
end;
Although this constructor does not have much code, it can be regarded as the most important member because the thread is created here.
After calling TObject.Create through Inherited, the first sentence is to call a process: AddThread, its source code is as follows:
procedureAddThread;
Begin
InterlockedIncrement(ThreadCount);
end;
There is also a corresponding RemoveThread:
procedureRemoveThread;
Begin
InterlockedDecrement(ThreadCount);
end;
Their function is very simple, which is to count the number of threads in the process by adding or decreasing a global variable. However, the commonly used Inc/Dec process used here is not the commonly used Inc/Dec process, but the InterlockedIncrement/InterlockedDecrement process is used. The functions they implement are exactly the same, both adding one or subtracting one to the variable. But there is one biggest difference between them, that is, InterlockedIncrement/InterlockedDecrement is thread-safe. That is, they can ensure that the execution results are correct under multithreading, but Inc/Dec cannot. Or in terms of operating system theory, this is a pair of "primitive" operations.
Take the addition one as an example to illustrate the difference in implementation details of the two:
Generally speaking, there are three steps after decomposing the operation of adding one to the memory data:
1. Read data from memory
2. Add one data
3. Save it in memory
Now suppose that an operation of adding one using Inc in a two-thread application may occur:
1. Thread A reads data from memory (assuming it is 3)
2. Thread B reads data from memory (also 3)
3. Thread A adds one to the data (now it is 4)
4. Thread B adds one to the data (now it is also 4)
5. Thread A stores data into memory (the data in memory is now 4)
6. Thread B also stores data into memory (the data in memory is still 4 now, but both threads add one to it, which should be 5, so an error result appears here)
There is no problem with the InterlockIncrement process, because the so-called "primitive" is an uninterruptible operation, that is, the operating system can ensure that no thread switching will be performed before an "primitive" is executed. So in the above example, only after thread A has finished storing data into memory, thread B can start to fetch the number from it and add one operation, which ensures that even in multi-threading situations, the result will definitely be It's correct.
The previous example also illustrates a situation of "thread access conflict", which is why threads need "synchronize". Regarding this, we will discuss it in detail later when talking about synchronization.
Speaking of synchronization, there is a distraction: Li Ming, a professor at the University of Waterloo in Canada, once raised objections to the translation of the word Synchronize as "synchronization" in "thread synchronization". I personally think what he said is actually very reasonable. In Chinese, "synchronization" means "occur at the same time", while "thread synchronization" is intended to avoid such "occur at the same time". In English, Synchronize has two meanings: one is synchronization in the traditional sense (Tooccuratthesametime), and the other is "coordination". The word Synchronize in "thread synchronization" should refer to the latter meaning, that is, "to ensure that multiple threads maintain coordination and avoid errors when accessing the same data." However, there are still many words like this that are not translated accurately in the IT industry. Since it has become a convention, this article will continue to be used. I will just explain it here, because software development is a meticulous task, and it should be clarified. Can't be ambiguous.
I'm going to go far, and I'll go back to the constructor of TThread. The most important sentence is this:
FHandle:=BeginThread(nil,0,@ThreadProc,Pointer(Self),CREATE_SUSPENDED,FThreadID);
Here we use the DelphiRTL function BeginThread mentioned above. It has many parameters, the key ones are the third and fourth parameters. The third parameter is the thread function mentioned above, that is, the code part executed in the thread. The fourth parameter is the parameter passed to the thread function, here is the created thread object (i.e. Self). Among other parameters, the fifth is used to set the thread to suspend after creation and not execute immediately (the work of starting the thread is determined in AfterConstruction based on the CreateSuspended flag), and the sixth is to return the thread ID.
Now let’s look at the core of TThread: the thread function ThreadProc. Interestingly, the core of this thread class is not a member of the thread, but a global function (because the parameter convention of the BeginThread process can only use global functions). Here is its code:
functionThreadProc(Thread:TThread):Integer;
var
FreeThread:Boolean;
Begin
try
ifnotThread.Terminatedthen
try
Thread.Execute;
except
Thread.FFatalException:=AcquireExceptionObject;
end;
Finally
FreeThread:=Thread.FFreeOnTerminate;
Result:=Thread.FReturnValue;
Thread.DoTerminate;
Thread.FFinished:=True;
SignalSyncEvent;
ifFreeThreadthenThread.Free;
EndThread(Result);
end;
end;
Although there is not much code, it is the most important part of the entire TThread because this code is code that is actually executed in a thread. The following is a line-by-line explanation of the code:
First, judge the Terminated flag of the thread class. If it is not marked as terminated, the Execute method of the thread class is called to execute the thread code. Because TThread is an abstract class and the Execute method is an abstract method, it is essentially executing the Execute code in the derived class.
Therefore, Execute is a thread function in a thread class. All code in Execute needs to be considered as thread code, such as preventing access violations.
If an exception occurs in Execute, the exception object is obtained through AcquireExceptionObject and stored in the FFatalException member of the thread class.
Finally, there is some finishing work done before the thread ends. The local variable FreeThread records the setting of the FreeOnTerminated property of the thread class, and then sets the thread return value to the value of the thread class return value attribute. Then execute the DoTerminate method of the thread class.
The code of the DoTerminate method is as follows:
procedureTThread.DoTerminate;
Begin
ifAssigned(FOnTerminate)thenSynchronize(CallOnTerminate);
end;
It is very simple, it is to call the CallOnTerminate method through Synchronize, and the code of the CallOnTerminate method is as follows, which is to simply call the OnTerminate event:
procedureTThread.CallOnTerminate;
Begin
ifAssigned(FOnTerminate)thenFOnTerminate(Self);
end;
Because the OnTerminate event is executed in Synchronize, it is essentially not thread code, but main thread code (see the analysis of Synchronize later).
After executing OnTerminate, set the FFinished flag of the thread class to True.
Next, execute the SignalSyncEvent process, and its code is as follows:
procedureSignalSyncEvent;
Begin
SetEvent(SyncEvent);
end;
It is also very simple, it is to set up a global Event: SyncEvent. Regarding the use of Event, this article will be described in detail later, and the purpose of SyncEvent will be in WaitFor