Home > Articles > Programming > C/C++

This chapter is from the book

Creating Multithreaded Applications

For Scrabble players, "multitasking" and "multithreading" might be a great opportunity to earn points. For developers, these terms are often sources of confusion and unnecessary headaches. I should emphasize unnecessary here because, when explained, they become part of the obvious programming concepts.

Understanding Multitasking

To put it simply, multitasking is the capability of the operating system to run multiple programs at the same time. Unconsciously, you've been using this capability while switching from your Microsoft Word document to Windows Explorer. Although multitasking might seem characteristic of graphical operating systems such as Windows or Linux, earlier computers also used multitasking to some extent. For example, Unix enables you to run multiple programs in the background.

Under Windows 3.x, applications used cooperative multitasking. Cooperative means that a program has control over the CPU and, before switching to another application, this program must finish processing data. This type of multitasking has a serious drawback: If an application stops responding, the entire operating system will hang. 32-bit versions of Windows solved this problem by introducing preemptive multitasking. A simple dictionary definition will help you understand its meaning:

preemptive: done before somebody else has had an opportunity to act.

In other words, to allow task-switching, 32-bit versions of Windows suspend the current application, whether it is ready to lose control or not.

NOTE

Cooperative multitasking is also called "nonpreemptive multitasking" for obvious reasons. Unlike preemptive multitasking, the operating system is unable to suspend an application that has stopped responding.

Understanding Multithreading

Multithreading is the capability of a program to run multiple tasks (threads) at the same time. Most Windows applications use only one thread, the primary thread. A primary thread takes care of child windows creation and message processing. All secondary threads are used to perform background operations: loading large files, looking for information, performing mathematical calculations.

WARNING

Throughout their learning process, young children tend to repeat words they have overheard here and there, simply to prove their knowledge or to resemble "big people." A similar situation occurs with programmers. Some developers tend to overuse programming techniques they've learned.

Do not use separate threads in your application unless you're dealing with lengthy background operations. Sometimes, with small code readjustments, you can simply avoid the use of threads. Why complicate your work? That said, multithreaded applications offer advantages, as you'll see later in this chapter.

Creating a Thread Using API Calls

You can create a new thread from another one by calling the CreateThread() API function. The CreateThread() parameters specify, among other things, the security attributes, the creation flags, and the thread function:

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  DWORD dwStackSize,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID lpParameter,
  DWORD dwCreationFlags,
  LPDWORD lpThreadId
  );

The SECURITY_ATTRIBUTES structure determines whether other processes can modify the object and whether a child process can inherit a handle to this object. If lpThreadAttributes is NULL, the thread gets the default security descriptor.

The dwCreationFlags parameter specifies the thread creation flags. If its value is CREATE_SUSPENDED, the thread will not run until you call the ResumeThread() function. Set this value to 0 to run the thread immediately after creation.

The lpThreadId parameter points to an empty DWORD that will receive the thread identifier. Under Windows NT/2000, if this parameter is NULL, the thread identifier is simply not returned. Windows 9x requires a DWORD variable. To ensure complete compatibility with the current operating system, do not use the NULL value.

The most crucial parameter is the starting function, also known as thread function. lpStartAddress is the address of the function that accepts one parameter and returns a DWORD exit code:

DWORD WINAPI ThreadFunc(LPVOID);

TIP

In a sense, the starting function can be compared to the main() or WinMain() function in a C++ program. ThreadFunc() is the main entry point for your thread.

Finally, dwStackSize and lpParameter specify the size of the stack (in bytes) and the parameter passed to the thread, respectively.

TIP

CreateThread(), as many other API calls, contains a large list of arguments more or less complex. In the beginning, understanding all aspects of this function can be disorienting.

A simple trick to overcome this problem is to first look at the arguments that can be zeroed. For example, in almost all parameters of CreateThread() except for lpStartAddress and lpThreadId, you can safely specify 0. After you fully understand these two arguments, you can always go back and further explore the CreateThread() function.

With the previous explanations and a little help from the Win32 Programmer's Reference help file, you should now be able to write a simple multithreaded application. Your example project should contain two buttons: Start and Stop. When the user clicks the Start button, this resumes the newly created thread. The thread should draw random ellipses and rectangles on the form. By clicking the Stop button, the user should be able to suspend the thread. Take a look at Listing 3.14. Don't forget that you can find the complete source code in the ThreadAPI folder of the companion CD-ROM.

Listing 3.14 ThreadFormUnit.cpp

#include <vcl.h>
#pragma hdrstop

#include "ThreadFormUnit.h"
#pragma package(smart_init)
#pragma resource "*.dfm"

TThreadForm *ThreadForm;
HANDLE Thread;

DWORD WINAPI ThreadFunc(LPVOID Param)
{
  HANDLE MainWnd(Param);

  RECT R;
  GetClientRect(MainWnd, &R);

  const MaxWidth = R.right - R.left;
  const MaxHeight = R.bottom - R.top;
  int X1, Y1, X2, Y2, R1, G1, B1;
  bool IsEllipse;

  while(true)
  {
   HDC DC = GetDC(MainWnd);

   X1 = rand() % MaxWidth;
   Y1 = rand() % MaxHeight;
   X2 = rand() % MaxWidth;
   Y2 = rand() % MaxHeight;

   R1 = rand() & 255;
   G1 = rand() & 255;
   B1 = rand() & 255;

   IsEllipse = rand() & 1;

   HBRUSH Brush = CreateSolidBrush(
     RGB(R1, G1, B1));
   SelectObject(DC, Brush);

   if(IsEllipse)
     Ellipse(DC, X1, Y1, X2, Y2);
   else
     Rectangle(DC, X1, Y1, X2, Y2);

   ReleaseDC(MainWnd, DC);
   DeleteObject(Brush);
  }
}

__fastcall TThreadForm::TThreadForm(TComponent* Owner)
  : TForm(Owner)
{
  randomize();

  DWORD Id;
  Thread = CreateThread(0, 0, ThreadFunc,
   ThreadForm->Handle, CREATE_SUSPENDED, &Id);

  if(!Thread)
  {
   ShowMessage("Error! Cannot create thread.");
   Application->Terminate();
  }
}

void __fastcall TThreadForm::StartClick(TObject *)
{
  ResumeThread(Thread);
  Start->Enabled = false;
  Stop->Enabled = true;
}

void __fastcall TThreadForm::StopClick(TObject *)
{
  SuspendThread(Thread);
  Stop->Enabled = false;
  Start->Enabled = true;
}

NOTE

As you can see in Listing 3.14, the code uses API functions almost exclusively. One of the reasons is that you must avoid accessing VCL properties and methods from secondary threads. I will describe why and provide a solution in the next section.

In the form constructor, you create a suspended thread using the CreateThread() function and check whether the new thread is valid or not. The Start and Stop buttons use the ResumeThread() and SuspendThread() API functions to modify the thread state. Finally, the thread function draws random shapes on the form's canvas. (Notice how the window handle is passed to ThreadFunc().) Figure 3.16 shows the project.

Figure 3.16Figure 3.16 The ThreadAPI project.

NOTE

Another efficient way to start a new thread is the _beginthread() routine defined in process.h (you can find process.h in the C++Builder installation Include folder):

unsigned long _beginthread(void (_USERENTRY *__start)(void *),
 unsigned __stksize, void *__arg);

NOTE

Because it requires fewer parameters, this function is commonly used in multithreaded applications.

Understanding the TThread Object

C++Builder encapsulates Windows thread objects into the TThread object. Creating a new thread is basically a matter of creating a new instance of a TThread descendant. Listing 3.15 contains the definition of the TThread abstract class.

Listing 3.15 TThread Class

class DELPHICLASS TThread;
class PASCALIMPLEMENTATION TThread : public System::TObject
{
  typedef System::TObject inherited;

private:
  unsigned FHandle;
  unsigned FThreadID;
  bool FTerminated;
  bool FSuspended;
  bool FFreeOnTerminate;
  bool FFinished;
  int FReturnValue;
  TNotifyEvent FOnTerminate;
  TThreadMethod FMethod;
  System::TObject* FSynchronizeException;
  void __fastcall CallOnTerminate(void);
  TThreadPriority __fastcall GetPriority(void);
  void __fastcall SetPriority(TThreadPriority Value);
  void __fastcall SetSuspended(bool Value);

protected:
  virtual void __fastcall DoTerminate(void);
  virtual void __fastcall Execute(void) = 0 ;
  void __fastcall Synchronize(TThreadMethod Method);
  __property int ReturnValue = {read=FReturnValue, write=FReturnValue,
     nodefault};
  __property bool Terminated = {read=FTerminated, nodefault};

public:
  __fastcall TThread(bool CreateSuspended);
  __fastcall virtual ~TThread(void);
  void __fastcall Resume(void);
  void __fastcall Suspend(void);
  void __fastcall Terminate(void);
  unsigned __fastcall WaitFor(void);
  __property bool FreeOnTerminate = {read=FFreeOnTerminate, write=
     FFreeOnTerminate, nodefault};
  __property unsigned Handle = {read=FHandle, nodefault};
  __property TThreadPriority Priority = {read=GetPriority, write=
     SetPriority, nodefault};
  __property bool Suspended = {read=FSuspended, write=SetSuspended,
     nodefault};
  __property unsigned ThreadID = {read=FThreadID, nodefault};
  __property TNotifyEvent OnTerminate = {read=FOnTerminate, write=
     FOnTerminate};
};

If you're wondering how to create a TThread descendant, the answer is simple. Open the File, New dialog and select Thread Object from the Object Repository. C++Builder will prompt you for the class name of the new descendant. Enter TRandomThread and click OK.

C++Builder will create automatically a new source file containing the TRandomThread object:

#include <vcl.h>
#pragma hdrstop

#include "Unit2.h"
#pragma package(smart_init)

__fastcall TRandomThread::TRandomThread(bool CreateSuspended)
  : TThread(CreateSuspended)
{
}

void __fastcall TRandomThread::Execute()
{

}

The Execute() method contains the code that will be executed when the thread runs. In other words, Execute() replaces your thread function. Also notice that the constructor of your object contains a CreateSuspended parameter. Just like the CREATE_SUSPENDED flag, when CreateSuspended is true, you must first call the Resume() method; otherwise, Execute() won't be called.

Tables 3.3 and 3.4 summarize the most common properties and methods of the TThread class.

Table 3.3 TThread Properties

Property

Description

FreeOnTerminate

Determines whether the thread object is automatically destroyed when the thread terminates.

Handle

Provides access to the thread's handle. Use this value when calling API functions.

Priority

Specifies the thread's scheduling priority. Set this priority to a higher or lower value when needed.

ReturnValue

Determines the value returned to other threads when the current thread object finishes.

Suspended

Specifies whether the thread is suspended or not.

Terminated

Determines whether the thread is about to be terminated.

ThreadID

Determines the thread's identifier.


Table 3.4 TThread Methods

Method

Description

DoTerminate()

Calls the OnTerminate event handler without terminating the thread.

Execute()

Contains the code to be executed when the thread runs.

Resume()

Resumes a suspended thread.

Suspend()

Pauses a running thread.

Synchronize()

Executes a call within the VCL primary thread.

Terminate()

Signals the thread to terminate.

WaitFor()

Waits for a thread to terminate.


Now it's time to try to use VCL objects exclusively.

You've already created a TRandomThread object, so use this object as the secondary thread of your application. The first step is to add the main unit's include file to the new thread unit. Select File, Include Unit Hdr and then select ThreadFormUnit.

There's not much to put in the TRandomThread constructor, except for the random numbers generator:

__fastcall TRandomThread::TRandomThread(bool
  CreateSuspended) : TThread(CreateSuspended)
{
  randomize();
}

Now take care of the core part of your thread: the Execute() method. You no longer need to determine the form size using the GetClientRect() API function. You can simply read the ClientWidth and ClientHeight properties:

  const MaxWidth = ThreadForm->ClientWidth;
  const MaxHeight = ThreadForm->ClientHeight;
  int X1, Y1, X2, Y2, R1, G1, B1;
  bool IsEllipse;

The TCanvas object, with which you're probably familiar, can greatly simplify the drawing process. There is a small problem: The VCL does not allow multiple threads to access the same graphic object simultaneously. Therefore, you must use the Lock() and Unlock() methods to make sure that other threads do not access the TCanvas while you're drawing:

  while(true)
  {
   ThreadForm->Canvas->Lock();

   X1 = rand() % MaxWidth;
   Y1 = rand() % MaxHeight;
   X2 = rand() % MaxWidth;
   Y2 = rand() % MaxHeight;

   R1 = rand() & 255;
   G1 = rand() & 255;
   B1 = rand() & 255;

   IsEllipse = rand() & 1;

   ThreadForm->Canvas->Brush->Color =
     TColor(RGB(R1, G1, B1));

   if(IsEllipse)
     ThreadForm->Canvas->Ellipse(X1, Y1,
      X2, Y2);
   else
     ThreadForm->Canvas->Rectangle(X1, Y1,
      X2, Y2);

   ThreadForm->Canvas->Unlock();
  }

This puts an end to the thread object code. Take a look now at the main unit. In the form constructor, you create a new instance of TRandomThread:

TRandomThread* Thread;

__fastcall TThreadForm::TThreadForm(TComponent* )
  : TForm(Owner)
{
  Thread = new TRandomThread(true);
  if(!Thread)
  {
   ShowMessage("Error! Cannot create thread.");
   Application->Terminate();
  }
}

The Start button calls the Resume() method:

void __fastcall TThreadForm::StartClick(TObject *)
{
  Thread->Resume();
  Start->Enabled = false;
  Stop->Enabled = true;
}

The Stop button calls the Suspend() method:

void __fastcall TThreadForm::StopClick(TObject *)
{
  Thread->Suspend();
  Stop->Enabled = false;
  Start->Enabled = true;
}

TIP

The C++Builder IDE provides a Threads debug window containing the list of available threads: their ID, state, location, and status. To display this window, choose View, Debug Windows, Threads from the C++Builder menu or press Ctrl+Alt+T.

The thread is automatically terminated when the Execute() function finishes executing or when the application is closed. To ensure that memory occupied by your thread object is freed on termination, always insert the following in the Execute() method:

  FreeOnTerminate = true;

Sometimes, however, you might need to terminate a thread by code. To do so, you could use the Terminate() method. Terminate() tells the thread to terminate by setting the Terminated property to true.

It is important to understand that Terminate() does not exit the thread by itself. You must periodically check in the Execute() method whether Terminated is true. For example, to terminate your TRandomThread object, add the following line:

  while(true)
  {
   if(Terminated) break;

Terminate() has the advantage of enabling you to do the cleaning by yourself, thus giving you more control over the thread termination. Unfortunately, if the thread stops responding, calling Terminate() will be useless.

The TerminateThread() API function is a more radical way to cause a thread to exit. TerminateThread() instantly closes the current thread without freeing memory occupied by the thread object. You should use this function only in extreme cases, when no other options are left. The TerminateThread() syntax is simple. Here is an example:

TerminateThread((HANDLE)Thread->Handle, false);

Understanding the Main VCL Thread

Properties and methods of VCL objects are not necessarily thread safe. This means that when accessing properties and methods, you can use memory that is not protected from other threads. Therefore, the main VCL thread should be the only thread to have control over the VCL.

NOTE

The main VCL thread is the primary thread of your application. It handles and processes Windows messages received by VCL controls.

NOTE

Graphic objects are exceptions to the thread-safe rule. By using the Lock() and Unlock() methods, other threads can be prevented from drawing on the canvas.

To allow threads to access VCL objects, TThread provides the Synchronize() method. Synchronize() performs actions contained in a routine as if they were executed from the main VCL thread:

void __fastcall Synchronize(TThreadMethod &Method);

Consider the example of a thread displaying increasing values in a Label component. Obviously, you'll use a for loop in the Execute() method. But, how will you change the Label's caption? By synchronizing with the VCL. Listing 3.16 contains the source code of the TLabelThread object, and Figure 3.17 shows the results.

Listing 3.16 TLabelThread Thread Object

#include <vcl.h>
#pragma hdrstop

#include "ThreadFormUnit.h"
#pragma package(smart_init)

#include <Classes.hpp>

class TLabelThread : public TThread
{
private:
protected:
  int Num;
  void __fastcall Execute();
  void __fastcall DisplayLabel();
public:
  __fastcall TLabelThread(bool CreateSuspended);
};
//-----------------------------------------------
__fastcall TLabelThread::TLabelThread(bool
  CreateSuspended) : TThread(CreateSuspended)
{
}

void __fastcall TLabelThread::DisplayLabel()
{
  ThreadForm->Label->Caption = Num;
}

void __fastcall TLabelThread::Execute()
{
  FreeOnTerminate = true;
  for(Num = 0; Num <= 1000; Num++)
  {
   if(Terminated) break;
   Synchronize (DisplayLabel);
  }
}

TIP

As opposed to the TRandomThread example, where you had an endless loop, in this project the thread is terminated when the value of 1000 is reached. By handling the OnTerminate event of TLabelThread, you can determine when the thread is about to exit:

void __fastcall TThreadForm::bStartClick(TObject *)

{
 Thread = new TLabelThread(false);
 Thread->OnTerminate = OnTerminate;
 bStart->Enabled = false;
}

void __fastcall TThreadForm::OnTerminate(TObject *)
{
 bStart->Enabled = true;
}

In fact, you can use OnTerminate as a replacement for the Synchronize() method. If your thread has actions to perform before exiting, OnTerminate will enable you to access VCL properties and methods from within the main unit.

Consider the previous example where you enabled the bStart button in the OnTerminate event handler. To accomplish the same thing directly from the thread object, you would have written a far more complex code:

// void __fastcall EnableButton();

void __fastcall TLabelThread::EnableButton()
{
 ThreadForm->bStart->Enabled = true;
}

void __fastcall TLabelThread::Execute()
{
 // ...
 if(Terminated)
 {
   Synchronize(EnableButton);
 // ...
}
Figure 3.17Figure 3.17 The LabelThread project.

Establishing Priorities

In an application using multiple threads, it is important to know which threads will have a higher priority and run first. Table 3.5 describes all possible priority levels.

Table 3.5 Thread Priorities

Priority Level

Description

THREAD_PRIORITY_TIME_CRITICAL

15 points above normal

THREAD_PRIORITY_HIGHEST

2 points above normal

THREAD_PRIORITY_ABOVE_NORMAL

1 point above normal

THREAD_PRIORITY_NORMAL

Normal

THREAD_PRIORITY_BELOW_NORMAL

1 point below normal

THREAD_PRIORITY_LOWEST

2 points below normal

THREAD_PRIORITY_IDLE

15 points below normal


All threads are created using the THREAD_PRIORITY_NORMAL. After a thread has been created, you can adjust the priority level higher or lower using the SetThreadPriority() function. A general rule is that a thread dealing with the user interface should have a higher priority to make sure that the application remains responsive to the user's actions. Background threads are usually set to THREAD_PRIORITY_BELOW_NORMAL or THREAD_PRIORITY_LOWEST so that they can be terminated when necessary.

NOTE

Priority levels are commonly called relative scheduling priorities because they are relative to other threads in the same process.

The TThread object provides a Priority property, which determines the thread priority level. Its possible values are

tpTimeCritical
tpHighest
tpHigher
tpNormal
tpLower
tpLowest
tpIdle

As you can see, they closely match the priority levels you previously described.

If you're still not convinced of the importance of thread priorities, take a look at the following example. Start a new application and add two progress bars (Max property set to 5000) and a Start button. You will try to increment the position of the progress bars using threads of different priorities. Listing 3.17 contains the source code of the TPriorityThread thread object.

Listing 3.17 TPriorityThread Thread Object

#include <vcl.h>
#pragma hdrstop

#include "PriorityThreadUnit.h"
#include "ThreadFormUnit.h"
#pragma package(smart_init)

__fastcall TPriorityThread::TPriorityThread(bool
  Temp) : TThread(false)
{
  First = Temp;
}

void __fastcall TPriorityThread::DisplayProgress()
{
  if(First)
   ThreadForm->ProgressBar1->Position++;
  else
   ThreadForm->ProgressBar2->Position++;
}

void __fastcall TPriorityThread::Execute()
{
  FreeOnTerminate = true;
  for(Num = 0; Num <= 5000; Num++)
  {
   if(Terminated) break;
   Synchronize (DisplayProgress);
  }
}

Notice that I slightly modified the TPriorityThread constructor. The Temp boolean variable (which replaces CreateSuspended) will indicate which progress bar should be accessed.

The main unit contains only the code for the Start button OnClick handler:

void __fastcall TThreadForm::bStartClick(TObject *)
{
  TPriorityThread *First;
  First = new TPriorityThread (true);
  First->Priority = tpLowest;

  TPriorityThread *Second;
  Second = new TPriorityThread(false);
  Second->Priority = tpLowest;

  bStart->Enabled = false;
}

Run the program and click the Start button. Both progress bars should reach the end at approximately the same time, as shown in Figure 3.18. Now, set the priority of the first thread to tpLower. Any difference? See the result in Figure 3.19.

Figure 3.18Figure 3.18 Threads with same priority.

Figure 3.19Figure 3.19 Threads with different priorities.

Timing Threads

Sometimes when developing it is useful to time sections of code. The basic principle is to record the system time before and after the code and subtract the start time from the end time to calculate the elapsed time. For general applications this can be done with the Win32 API function GetTickCount(). This is illustrated in Listing 3.18.

Listing 3.18 Timing Code with GetTickCount()

  int Start = GetTickCount();

  // ...
  Form1->Canvas->Lock();
  for(int x = 0; x <= 100000; x++)
    Form1->Canvas->TextOut(10, 10, x);
  Form1->Canvas->Unlock();
  // ...

  int Total = GetTickCount() - Start;
  ShowMessage(FloatToStr(Total / 1000.0) + " sec");

A similar example could be created using the clock() instead of GetTickCount(). There are also other functions that can be used to time code.

Unfortunately, because of the preemptive behavior of Windows, threads are often interrupted. For this reason, you can't rely on GetTickCount() to retrieve the thread execution time. However, Windows provides the GetThreadTimes() API function, which helps you time your threads:

BOOL GetThreadTimes(
  HANDLE hThread,
  LPFILETIME lpCreationTime,
  LPFILETIME lpExitTime,
  LPFILETIME lpKernelTime,
  LPFILETIME lpUserTime
  );

WARNING

GetThreadTimes() is available only with Windows NT/2000.

As you can see, GetThreadTimes() uses the FILETIME structure. Before performing arithmetic operations, you must first store the user time information in a LARGE_INTEGER. Then, by subtraction of the 64-bit QuadPart members of the LARGE_INTEGER structure, you could obtain the number of 100 nanoseconds that your code takes to execute. Listing 3.19 illustrates this.

Listing 3.19 GetThreadTimes() Example

  FILETIME CreationTime, ExitTime, KernelTime;
  union {
   LARGE_INTEGER iUT;
   FILETIME fUT;
  } UserTimeS, UserTimeE;

  GetThreadTimes((HANDLE)Handle, &CreationTime,
   &ExitTime, &KernelTime, &UserTimeS.fUT);

  // ...
  Form1->Canvas->Lock();
  for(int x = 0; x <= 100000; x++)
   Form1->Canvas->TextOut(10, 10, x);
  Form1->Canvas->Unlock();
  // ...

  GetThreadTimes((HANDLE)Handle, &CreationTime,
   &ExitTime, &KernelTime, &UserTimeE.fUT);

  float Total = UserTimeE.iUT.QuadPart - UserTimeS.
   iUT.QuadPart;
  Total /= 10 * 1000 * 1000; // Converts to seconds

  OutputDebugString(FloatToStr(Total).c_str());

TIP

OutputDebugString() is a useful API function that sends a string to the Event Log debug window. Under normal circumstances, I have the tendency to use message boxes or to change the window caption, but in multithreaded applications these actions can sometimes be disastrous without considerable coding. OutputDebugString() is, therefore, a perfect alternative.

TIP

The OutputDebugString() function is covered in more detail in win32.hlp, which is part of the C++Builder installation in the Borland Shared\MSHelp directory.

Synchronizing Threads

Probably the greatest disadvantage of using threads is the difficulty in organizing them. Let's say your application is simultaneously running two threads, which modify some global data. What will happen if they try to access the same data at the same time? Or, what if the second thread has to wait for the first thread to process this data, and then execute? To coordinate threads, Windows offers various methods of synchronization.

Critical Sections

To illustrate two threads accessing the same global data, you'll create a sample application using the TCriticalThread object (see Listing 3.20).

Listing 3.20 CriticalThreadUnit.cpp: TCriticalThread Thread Object

#include <vcl.h>
#pragma hdrstop

#include "CriticalThreadUnit.h"
#include "ThreadFormUnit.h"

#pragma package(smart_init)

__fastcall TCriticalThread::TCriticalThread(bool CreateSuspended)
  : TThread(CreateSuspended)
{
}

void __fastcall TCriticalThread::DisplayList()
{
  ThreadForm->ListBox->Items->Add(Text);
}

void __fastcall TCriticalThread::Execute()
{
  FreeOnTerminate = true;

  for(int x = 0; x <= 50; x++)
  {
   if(Terminated) break;

   // EnterCriticalSection(&ThreadForm->CS);
   Sleep(50);
   ThreadForm->ListText.Insert("=====", 1);
   Text = ThreadForm->ListText;
   Synchronize(DisplayList);
   ThreadForm->ListText.SetLength(ThreadForm->
     ListText.Length() - 5);
   // LeaveCriticalSection(&ThreadForm->CS);
  }
}

And in your main unit, you'll create two instances of this object (see Listing 3.21).

Listing 3.21 ThreadFormUnit.cpp

#include <vcl.h>
#pragma hdrstop

#include "ThreadFormUnit.h"
#include "CriticalThreadUnit.h"

#pragma package(smart_init)
#pragma resource "*.dfm"

TThreadForm *ThreadForm;

__fastcall TThreadForm::TThreadForm(TComponent* Owner)
  : TForm(Owner)
{
  ListText = "=====";
  // InitializeCriticalSection(&CS);
}

void __fastcall TThreadForm::StartClick(TObject *Sender)
{
  TCriticalThread *FirstThread;
  FirstThread = new TCriticalThread(false);

  TCriticalThread *SecondThread;
  SecondThread = new TCriticalThread(false);
}

void __fastcall TThreadForm::FormClose(TObject *,
   TCloseAction &Action)
{
  // DeleteCriticalSection(&CS);
}

Your code is both simple and useless, but it will demonstrate the importance of thread synchronization. First, the TCriticalThread object adds to the global ListText variable five equals (=) characters. Then, it adds the value of ListText to a ListBox. Finally, TCriticalThread() truncates five characters, thus setting ListText to the value it initially had. Logically, all ListBox items should display ==========, but as Figure 3.20 shows, that's not always the case. Why? Because the second thread also accesses the same global variable.

Figure 3.20Figure 3.20 The CriticalThread project without critical sections.

Critical sections are an easy and efficient way to temporarily block other threads from accessing data (similar to the Lock() and Unlock() methods for graphic objects). To define a critical section, you'll use four basic API functions:

VOID InitializeCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
  );

VOID EnterCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
  );

VOID LeaveCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
  );

VOID DeleteCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
  );

It's not so difficult to guess how to use these functions. First, you declare a variable of type CRITICAL_SECTION. You initialize this variable at program startup (InitializeCriticalSection()) and delete it when the program closes (DeleteCriticalSection()). When your thread starts processing data, you block access to other threads with EnterCriticalSection() and, when it finishes, you exit the critical section (LeaveCriticalSection()).

Go back to Listings 3.20 and 3.21, and comment out the four lines, which call the critical section functions I described. Then, open the header file of your main unit and add the following line:

  CRITICAL_SECTION CS;

As shown on Figure 3.21, all ListBox items now contain the same string.

Figure 3.21Figure 3.21 The CriticalThread project with critical sections.

Mutexes

Mutexes offer the functionality of critical sections, while adding other interesting features.

TIP

Although featureless, critical sections are slightly faster then mutexes and semaphores. If time is an important factor in your application, consider using critical sections.

Mutex objects are created using the CreateMutex() API function:

HANDLE CreateMutex(
  LPSECURITY_ATTRIBUTES lpMutexAttributes,
  BOOL bInitialOwner,
  LPCTSTR lpName
  );

After you have the handle of the newly created mutex object, you must use the WaitForSingleObject() function. This API call will request ownership for the mutex object, wait until this object becomes available, and use the mutex until ReleaseMutex() is called:

  HANDLE Mutex;
  Mutex = CreateMutex(NULL, false, NULL);
  if(Mutex == NULL)
  {
   ShowMessage("Cannot create mutex!");
   return;
  }

  // ...

  if(WaitForSingleObject(Mutex, INFINITE) ==
   WAIT_OBJECT_0)
  {
   // ...
  }

  ReleaseMutex (Mutex);

NOTE

Unlike critical sections, two or more processes can use the same mutex.

NOTE

If a thread doesn't release its ownership of a mutex object, this mutex is considered to be abandoned. Therefore, WaitForSingleObject() will return WAIT_ABANDONED. Although it's not perfectly safe, you can always acquire ownership of an abandoned mutex.

Others

Other synchronization objects such as semaphores and timers are also available. By familiarizing yourself with critical sections and mutexes, you'll already be one step ahead into mastering thread synchronization.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020