Threads in Delphi - (4)

zhaozj2021-02-08  191

Thread class in Delphi

Raptor [Mental Studio] http://ental.mentsu.com

CriticalSection is a technology that shares data access protection. It is actually equivalent to a global Boolean variable. However, it is different from it, it only has two operations: Enter and Leave, which can also use two states as True and False, indicating whether it is in the critical area, respectively. These two operations are also primitives, so it can be used to protect shared data in multi-threaded applications to prevent access violations.

Method for protecting data with critical regions is simple: Calling Enter settings to enter the critical zone flag each time you have to access shared data, then operate the data, and finally call Leave to leave the critical area. Its protection principle is this: When a thread enters the critical area, if another thread is to access this data, it will find the thread to enter the critical area when calling Enter, and then this thread will be Hang up, wait for the thread calling at the critical area to call Leave leaves the critical area, when another thread is completed, this thread will be woken up, and set the critical area flag to start the data, which prevents access. conflict.

Take the previous InterlockedIncrement as an example, we use the CriticalSection (Windows API) to implement it:

VAR

InterlockedCrit: TrtlcriticalSection;

Procedure InterlockedIncrement (VAR Avalue: Integer);

Begin

ENTERCRITICALSECTION (InterlocKedcrit);

INC (AVALUE);

LeavecriticalSection (InterlockedCrit);

END;

Now let's take a look at the example:

1. Thread A enters the critical area (assuming data 3)

2. Thread B enters the critical area, because A is already in the critical area, so B is hanging

3. Thread A Plus the data (now 4)

4. Thread a Leave the critical area and wake up thread B (now the data in memory is 4)

5. Thread B is awakened, add one for data (now 5)

6. Thread B leaves the critical area, and the current data is correct.

The critical area is to protect access to sharing data.

With regard to the use of the critical regions, it is important to pay attention to: ie the abnormal conditions of data access. Because if an exception occurs in the data operation, the Leave operation will result in not being executed, and the result will make the thread that should be woken out is not awakened, which may cause no response of the program. So, in general, as this is the right practice as this is the right practice:

ENTERCRITICALSECTION

Try

// Operate the critical area data

Finally

LeaveCriticalSection

END;

Finally, it will be described that Event and criticalSECTION are operating system resources. You need to be created before use. It also needs to be released after use. For example, a global Event: Syncevent and global criticalsection: Thetsynchronization and DonThreadsynchronization are created and released in the initThreadsynchronization and DonThreadsynchronization, while they are called in the initialization and finalization of the Classes unit. Since both the API is used in TTHREAD, Event and CriticalSecion are used, so that the API is an example, in fact, Delphi has provided packages for them, in the Syncobjs unit, are the TEVENT class and the TcriticalSECTION class. The usage is also different from the method of using the API in front. Because of the TEVENT's constructor parameters, Delphi provides an Event class initialized with the default parameters: TsIMpleEvent.

By the way, introduce another class for thread synchronization: TMULTIREXCLUSIVEWRITESYNCHRONIZER, it is defined in the SYSUTILS unit. As far as I know, this is the longest class name defined in Delphi RTL, but it has a short alias: Tmrewsync. As for its use, I want to see the name, I can know, I will not say much.

With the preparation knowledge of Event and CriticalSection, you can formally began to discuss Synchronize and WaitFor.

We know that SYNCHRONIZE is performed by putting some code in the main thread, because in a process, there is only one main thread. Let's take a look at the implementation of Synchronize:

Procedure tthread.synchronize (Method: TthreadMethod);

Begin

Fsynchronize.fthread: = Self;

FSYNCHRONIZE.FSYNCHRONIZEEXCEPTION: = NIL;

Fsynchronize.fmethod: = method;

Synchronize (@fsynchronize);

END;

Where fsynchronize is a record type:

Psynchronizerecord = ^ TsynchronizeRecord;

TsynchronizeRecord = Record

FTHREAD: TOBJECT;

Fmethod: tthreadmethod;

FSYNCHRONIZEEXCEPTION: TOBJECT;

END;

Data exchange between threads and main threads, including incoming thread class objects, synchronous methods, and exceptions occurred.

The overload version is called in Synchronize, and this overload version is quite special, it is a "class method". The so-called method is a special class member method, and its call does not need to create class instance, but is called by class name as constructor. The reason why it will be used to implement it because it can be called when the thread object is not created. However, it is actually another overload version (also class method) and another class method StaticSynchronize. Below is this code of this SYNCHRONIZE:

Class Procedure Tthread.Synchronize (asynchronize);

VAR

SyncProc: TsyncProc; Begin

IF getcurrentthreadidiD = mainthreadid then

AskYNCREC.FMETHOD

Else

Begin

SyncProc.Signal: = CreateEvent (NIL, True, False, NIL);

Try

ENTERCRITICALSECTION (Threadlock);

Try

IF synclist = nil dam

Synclist: = tList.create;

SyncProc.syncRec: = asyncRec;

Synclist.add (@SyncProc);

SignalsyncEvent;

If Assigned (WakemainThread) THEN

Wakemainthread (syncproc.syncRec.fthread);

LeavecriticalSection (Threadlock);

Try

WaitforsingleObject (SyncProc.signal, Infinite);

Finally

ENTERCRITICALSECTION (Threadlock);

END;

Finally

LeavecriticalSection (Threadlock);

END;

Finally

CloseHandle (SyncProc.SIGNAL);

END;

IF assigned (asyncRec.fsynchronizeexception)..............

END;

END;

This code is slightly more, but it is not too complicated.

The first is to determine if the current thread is a primary thread. If so, simply perform the synchronization method and return.

If it is not a main thread, it is ready to start the synchronization process.

Recording thread switched data (parameters) and an EVENT HANDLE via a local variable SyncProc, the record structure is as follows:

TsyncProc = Record

SyncRec: psynchronizecord;

Signal: thandle;

END;

Then create an Event, then enter the critical area (through the global variable threadlock, because only one thread can enter the SYNCHRONIZE status, you can record this record data into the list of SyncList (if this If the list does not exist, it creates it). It can be seen that this critical area of ​​Threadlock is to protect access to synclist, which will be seen again when introducing Checksynchronize later.

Next, it is called SignalsyncEvent, and its code has been introduced when the constructor of TTHREAD is described earlier, and its function is to simply make SYNCEVENT operations. The use of this SYNCEVENT will be described in detail when WaitFor will be described later.

The next step is the most important part: call the WakemainThread event to synchronize. WakemainThread is a global event for TnotifyEvent type. The reason why the SYNCHRONIZE method is used here is because the Synchronize method is inherently through the message, and the process that needs to be synchronized is executed in the main thread. If there is no message loop, it is unusable. Therefore, use this event to process.

In response to this event, the Application object, the following two methods are used to set and empty the response of the WakemainThread event (from the Forms unit): Procedure Tapplication.hookysynchronizewakeup;

Begin

Classes.wakemainthread: = wakemainthread;

END;

Procedure tapplication.unhookysynchronizewakeup;

Begin

Classes.wakemainthread: = NIL;

END;

The above two methods are called in the constructor of the TAPPLICATION class and the destructor.

This is the code that responds to the WakemainThread event in the Application object, the message is sent here, which uses an empty message to implement:

Procedure Tapplication.wakemainthread (Sender: TOBJECT);

Begin

Postmessage (Handle, WM_NULL, 0, 0);

END;

And the response of this message is also in the Application object, see the code below (delete unrelated part):

Procedure Tapplication.WndProc (Var Message: TMESSAGE);

...

Begin

Try

...

WITH MESSAGE DO

Case msg of

...

WM_NULL:

Checksynchronize;

...

Except

HandleException (Self);

END;

END;

The CheckSynchronize is also defined in the CLASSES unit, because it is more complicated, temporarily unknown, as long as it is a part of the specific handling Synchronize function, continue to analyze the SYNCHRONIZE code.

After executing the WakemainThread event, you will withdraw from the critical zone and then call WaitForsingleObject to start waiting at the Event created before entering the critical area. The feature of this EVENT is to wait for the execution of this synchronization method, with this point, after analyzing CheckSynchronize, will then explain.

Note that after WaitforsingleObject, re-enter the critical regions, but if you don't do anything, you will seem to have no meaning, but this is a must!

Because the Enter and Leave of the critical regions must be strict. So can you change it?

If Assigned (WakemainThread) THEN

Wakemainthread (syncproc.syncRec.fthread);

WaitforsingleObject (SyncProc.signal, Infinite);

Finally

LeavecriticalSection (Threadlock);

END;

The above code and the original code have the difference in the limitations of WaitForsingleObject to the critical regions. It seems that there is no impact, but also makes the code greatly, but can you really?

In fact, it's not!

Because we know, after the ENTER critical area, if other threads have to enter, they will be suspended. The WaitFor method will hang the current thread until you wait another thread to set the STEVENT will be awakened. If it is changed to the above code, if that setEvent's thread also needs to enter the critical region, the deadlock has happened (regarding the theory of deadlock, please refer to the information of the operating system principle).

Death lock is one of the most important aspects of threads!

The event created at the end of the final release, if the synchronous method returns an abnormality, it will throw an exception again.

(to be continued)

转载请注明原文地址:https://www.9cbs.com/read-2669.html

New Post(0)