Разработка многопоточных приложений в C++Builder

C++Builder предоставляет несколько объектов, которые делают разработку многопоточных приложений проще.
Для создания многопоточных приложений в C++Builder реализован абстрактный класс TThread.
TThread — абстрактный класс, который допускает создание отдельных потоков выполняющихся в приложении.
Создайте потомка класса TThread, чтобы представить выполняемый поток в многопоточном приложении.
Каждый новый экземпляр потомка TThread — новый поток выполнения.
Множество экземпляров, полученные от класса TThread , делает C++Builder многопоточным приложением.

Вы должны создать новый объект класса, производный от TThread.
Для этого:

Выберите File | New | Other | Thread Object , чтобы создать новый модуль, содержащий объект, производный от класса TThread,
вам предложат как-то назвать этот класс, назовите как вам нравится (например TMyThread)
Будет создан новый модуль, содержащий описание этого класса TMyThread, его конструктор и метод Execute()

__fastcall TMyThread::TMyThread(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
//---------------------------------------------------------------------------
// B метод объекта Execute(), вставьте код, который должен выполняться, когда поток выполняется.
void __fastcall TMyThread::Execute()
{
//---- Place thread code here ----
}
//---------------------------------------------------------------------------


В основной программе создайте объект этого потокового класса
// создаем поток в приостановленном состоянии (true), запущенном (false)

TMyThread *Thr = new TMyThread(true); // в приостановленном

Если вы заметили, в конструкторе есть параметр bool CreateSuspended, если при создании
объекта этот параметр имеет значение false, поток сразу — при создании объекта начнет свою работу,
то есть начнется выполнение кода в методе Execute(), если параметр bool CreateSuspended true, будет создан
поток в приостановленном состоянии, для запуска потока вам требуется применить методом Resume()
Thr->Resume();

Также, например в конструкторе, Вы можете указать(изменить) приоритет потока  — то есть сколько процессорного времени
будет выделять операционная система для вашего потока, относительно других потоков вашего приложения
- за это отвечает свойство Priority, по умолчанию все потоки создаются с нормальным приоритетом.

Thr->Priority = tpLower; // установить приоритет ниже нормального
Thr->Resume();                        // запустить поток выполняться


Приоритеты могут иметь следующие значения:

Значение Приоритет
tpIdle поток выполняется только, когда система — простаивает. Windows не будет прерывать другие потоки, чтобы выполнить поток с tpIdle приоритетом.
tpLowest приоритет потока — на два пункта ниже нормального.
tpLower приоритет потока — на один пункт ниже нормального.
tpNormal нормальный приоритет
tpHigher приоритет потока — на один пункт выше нормального.
tpHighest приоритет потока — на два пункта выше нормального.
tpTimeCritical поток получает самый высокий приоритет

Приостановить поток можно методом Suspend(), запустить поток методом Resume()
Выполнение потока автоматически завершается после завершения функции Execute() или закрытии приложения.

Чтобы занятая потоком память освобождалась при завершении потока используйте в Execute() FreeOnTerminate = true;
Однако, возможны ситуации, когда завершение потока должно быть скоординировано с другим потоком. Например, Вы должны ждать возвращения значения из одного потока, чтобы возвратить это значение в другой поток. Чтобы сделать это, Вы не освобождаете первый поток, пока второй поток не получит возвращаемое значение. Вы можете обработать эту ситуацию, установив FreeOnTerminate=false и затем явно освободив первый поток из второго.

Чтобы прекратить выполнение потока, не дожидаясь его завершения, например из другого потока, используйте метод Terminate().
Thr->Terminate();
Метод Terminate() задает значение true для свойства Terminated, то есть Вам самому необходимо в потоке
(в методе Execute) периодически проверять значение Terminated и если это значение стало true,
предприянять необходимые действия, например завершить поток, т.е. выйти из Execute()
Например, так

void __fastcall TMyThread::Execute()
{
     FreeOnTerminate = true; // освободить занятую потоком память по окончании его работы
     for(int i=0; i<10000; i++)
     {
      // -- какие-то сложные вычисления в цикле
      if(Terminated) break; // прервать- завершить поток
     }
}

В экстремальных ситуациях, для завершения работы потока используйте API-функцию TerminateThread().
Эта функция закрывает текущий поток без освобождения памяти, занятой потоком процесса.
Синтаксис ее: TerminateThread((HANDLE)Thr->Handle, false);

Теперь об особенностях работы потоков в приложениях C++Builder ( бибилиотека VCL )

Как известно при написании программ на C++Builder (и Delphi) обычно вы пользуетесь бибилиотекой VCL.
(например компонентами из палитры компонентов)
Когда Вы используете объекты из иерархий VCL или CLX, их свойства и методы, не гарантируется безопасность потока.
То есть, обращаясь к свойствам или выполняя методы этих объектов, могут выполнятся некоторые действия, которые используют память, которая не защищена от действий других потоков.
А значит, основной поток библиотеки VCL должен быть единственным потоком, управляющим этой библиотекой.
(он-же является первичным потоком вашего приложения)
Он обрабатывает все сообщения Windows, полученные компонентами в вашем приложении.
Как-же тогда безопасно из потока получить доступ к управлению свойствами и методами VCL-объектов (компонентов) ?
Для этого в TThread предусмотрен метод Synchronize()

void __fastcall TMyThread::Execute()
{
FreeOnTerminate = true; // освободить занятую потоком память по окончании его работы
for(int i=0; i<10000; i++)
{
// -- какие-то сложные вычисления в цикле
// ---
if(Terminated) break; // прекратить извне поток
Synchronize(pb); // позволяет получить доступ к свойствам и методам VCL-объектов
}
}
//---------------------------------------------------------------------------

void __fastcall TMyThread::pb()
{
static int n = 0;

n++;
Form1->Label1->Caption = n;
Application->ProcessMessages();
}
//-----


Обратите внимание: Поскольку Synchronize использует цикл сообщений, это не работает в консольных приложениях. Вы должны использовать другие механизмы, типа критических разделов, для защиты доступа к объектам VCL или CLX в консольных приложениях

Не надо использовать Synchronize метод для следующих объектов:

Синхронизация потоков.

Помимо координации работы потоков с помощью приоритетов потоков в приложении также часто бывает необходимым синхронизировать потоки. Что имеется ввиду ?
Координация совместной работы нескольких потоков, если например они пытаются одновременно что-то сделать,
например вывести что-либо на форму, получить доступ к глобальным данным и т.д.
Для этого используются такие объекты, как критические разделы ( critical section ), мьютексы ( mutex ) ,
семафоры ( semaphore ), таймеры.

Критические разделы. ( critical section )

Для создания и использования критического раздела, нужно объявить переменную типа CRITICAL_SECTION,
в нашем примере в Unit1.h
...
public: // User declarations
      CRITICAL_SECTION CS;
...

// Потом эту переменную CS нужно инициализировать (создать критический раздел)
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
     InitializeCriticalSection(&CS);
}


// используем критический раздел в потоке, когда нужно блокировать доступ к данным
void __fastcall TMyThread::Execute()
{
   FreeOnTerminate = true; // освободить занятую потоком память по окончании его работы
   for(int i=0; i<10000; i++)
   {
     // -- какие-то сложные вычисления в цикле
    if(Terminated) break; // прекратить извне поток
     EnterCriticalSection(&Form1->CS); // блокировать доступ к данным (войти в критический раздел)
     ...  доступ к глобальным данным
     LeaveCriticalSection(&Form1->CS); // закрыть критический раздел (покинуть критический раздел)
   }
}


Когда критический раздел становиться не нужен, удаляем его
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
     DeleteCriticalSection(&CS); // удалить критический раздел
}


Мьютексы. ( mutex )

Мьютексы выполняются медленнее критических разделов, однако они обладают большими возможностями,
чем критические разделы. Так, например, они могут использоваться разными процессами.
Создаются они с помощью API-функции CreateMutex(), работа с ними также осуществляется с помощью
API-функций, таких как WaitForSingleObject(), ReleaseMutex(), и др.
Об использовании мьютексов и семафоров см. Разработка многопоточных приложений в Windows.


Описание класса TThread ( Находится в Classes.hpp )

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

private:
unsigned FHandle;
unsigned FThreadID;
bool FCreateSuspended;
bool FTerminated;
bool FSuspended;
bool FFreeOnTerminate;
bool FFinished;
int FReturnValue;
TNotifyEvent FOnTerminate;
TThreadMethod FMethod;
System::TObject* FSynchronizeException;
System::TObject* FFatalException;
void __fastcall CheckThreadError(int ErrCode)/* overload */;
void __fastcall CheckThreadError(bool Success)/* overload */;
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);
virtual void __fastcall AfterConstruction(void);
void __fastcall Resume(void);
void __fastcall Suspend(void);
void __fastcall Terminate(void);
unsigned __fastcall WaitFor(void);
__property System::TObject* FatalException = {read=FFatalException};
__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};
};



Основные свойства и методы класса TThread

Свойство Описание
FreeOnTerminate Указывает, будет ли объект потока автоматически удален при завершении работы потока
Handle Дескриптор потока, для вызова API-функций
Priority Задает приоритет потока
ReturnValue Определяет значение, возвращаемое другим потокам, после завершения текущего потока
Suspended Указывает, приостановлено-ли выполнение потока
Terminated Определяет, может ли прекращаться выполнение потока
ThreadID Определяет идентификатор потока

Метод Описание
DoTerminate() Вызывает обработчик события OnTerminate() без прекращения работы потока
Execute() Содержит код, который будет выполнен при запуске потока
Resume() Возобновляет работу приостановленного потока
Suspend() Приостанавливает работу потока
Synchronize() Выполняет вызов в первичном потоке библиотеки VCL
Terminate() Сигнализирует о прекращении выполнения потока
WaitFor() Ждет прекращения работы потока
есть и другие менее используемые свойства и методы.

Предупреждение: исключения, которые возбуждены, но не перехвачены в TThread потомке при выполнении в методе Execute могут стать причиной access violations, когда Вы выполняете приложение вне интегрированной среды разработки. Вы можете принять меры против этого access violation, обеспечив метод Execute блоком try.._finally, который включает тело этого метода.

Пример работы простого многопоточного приложения (C++Builder 6).