Windows Synchronization Techniques
The Win32 API functions offer many further synchronization techniques:
•
Critical Sections are portions of source code that cannot be executed by two threads at the same time. By using a critical section, you can serialize the executeion of specific portions of the source code. Critical sections can be used only within a single process, a single application.
•
Mutexes are global objects you can use to serialize access to a resource. You first set a mutex, then access the resource, and finally release the mutex, as we saw in the OneCopy example. While the mutex is set, if another thread (or process) tries to set the same mutex, it is stopped until the mutex is released by the previous thread (or process). A mutex can be shared by different applications.
•
Semaphores are similar to mutexes, but they are counted: yu could allow, for example, three and no more than three accesses to a given resource at the same time. A mutex is equivalent to a semaphore with a maximum count of 1. We'll see an example of the use of the semaphore in the section "threaded database acces" (dus niet - hezik) later in this chapter.
•
Events can be used as a mean of synchronizing a thread with system events, such as user file operations. The WaitFor method of the Delphi TThread class uses an event. Events can also be used to
awaken several threads at the same time.
Building an Example
To demonstrate all of these different techniques I've built the ThSynch example. Suppose we have two threads operationg on a string, both of them using its value in some way and then updating the string as a result of the operation. Suppose also that the current value of the string is shared by the two threads. In example the string intially contains 20 A characters, then is update to contain 20 B characters, and so on. Each thread simply computes the next value of the string and then send it to its own list box.
The ThSynch example presents a main form with four buttons, each of hich displays a secondary form demonstrating one of the syncrhronization techniques. Each secondary form consists of two list boxes, where the text is displayed in a nonproportional Courier font, and a button to start the related thread. So we end up with four different versions of basically the same form and the same thread class. In each of these forms the STart button simply creates two instances of a thread, associating a list box with the LBox public field of each:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
| procedure TForm2.BtnStartClick(Sender: TObject);
begin
Listbox1.Clear;
Listbox2.Clear;
Th1 := TListThread.Create(True;
Th2 := TListThread.Create(True;)
Th1.FreeOnTerminate := True;
Th2.FreeOnTerminate := True;
Th1.Lbox := Listbox1;
Th2.Lbox := Listbox2;
Th1.Resume;
Th2.Resume;
end; |
Notice that the FreeOnTerminate property is set to True, so that the thread object will automatically free itself when its execution is completed. The thread classes are declared in each of the units defining the form, to avoid having to many files. This same unit contains the string variable used by the two thread objects:
Delphi:
1
2
| var
Letters : string ='AAAAAAAAAAAAAAAAAAAA'; |
This is far from elegant, but in this program we are looking for trouble on purpose, so forget the coding style.
The Plain Thread
Here is the thread class and its execute method in a first simple version:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| type
TListTread = class(TThread)
private
Str: String;
protected
procedure AddToList;
procedure Execute; override;
public
LBox: TListbox;
end;
procedure TListThread.Execute;
var
I,J,K: Integer;
begin
for i := 0 to 50 do
begin
for j = 1 to 20 do
for k=1 to 2601 do // useless repetition ...
if Letters [J] <> 'Z' then
Letters[J] := Succ(Letters[J])
else
Letters[J] := 'A';
Str := Letters;
Synchronize (AddToList);
end;
end; |
The AddToList method simply adds the Str string to the list box connected with the trhread. I've made each computation artifically long, by increasing each letter 2601 times instead of once: The effect is the same, but there are more chances of having a conflict between the two threads. You can see this effect in figure 25.5. Even better, you can press the Start button two or three times in a row, starting several threads at once, and increasing the chance of errors.
Using Critical Sections
If you want to serialize the operations of the two threads, and have more control over what the threads do, you can use one of the Windows Synchronization techniques i've discussed before. In this second version I'll use critical sections. To do this you should add the declaration of another global variable (or a form class field) for the critical section:
Delphi:
1
2
| var
Critical1: TRTLCriticalSection; |
This variable is initialized when the form is created, and it is destroyed at the end with two api calls:
Delphi:
1
2
3
4
5
6
7
8
9
| procedure TForm3.FormCreate(Sender: TObject);
begin
InitializeCriticalSection (Critical1);
end;
procedure TForm3.FormDestroy (Sender: TObject);
begin
DeleteCriticalSection (Critical1);
end; |
You can use critical sections to serialize specific portions of the code, such as the code that updates each letter of the string. Here is how I've updated the code of the Execute method of the thread object:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| procedure TListThread.Execute;
var
I,J,K: Integer;
begin
for i := 0 to 50 do
begin
EnterCriticalSection (Critical1);
try
for j = 1 to 20 do
... //same code as above
Str := Letters;
finally
LeaveCriticalSection (Critical1);
end;
Synchronize (AddToList);
end;
end; |
The effect of this code is that while one thread is computing a new string the other thread will wait before doing the same, so all the output strings will always contain 20 copies of the same character.
Using a mutex
Now we can write the same code, but using a mutex instead of a critical section. The effect will be the same, but I'd like to show you this technique as well. We simple need to declare the hMutex variable of type THandle and then replace the four API calls of the previos example with the following four:
Delphi:
1
2
3
4
5
6
7
8
| // in formcreate
hMutex := CreateMutex (nil, false, nil);
// in formdestroy
CloseHandle (hMutex);
// in the for loop
WaitForSingleObject (hMutex, INFINITE);
...
ReleaseMutex(hMutex); |