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).