Студопедия

Главная страница Случайная страница

КАТЕГОРИИ:

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






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






Также, при создании потоков следует учитывать, что слежение за слишком многими потоками потребляет процессорное время; рекомендованный предел - 16 активных потоков на процесс на системах с одним процессором.

При планировании системой работы с потоками учитываются их приоритеты. Поток может иметь приоритет в диапазоне от 0 - самый низкий, до 31 - самый высокий. При выборе очередного кандидата на выделение ему кванта времени система, прежде всего, смотрит, нет ли готового к выполнению потока с приоритетом 31. Если такой поток обнаруживается, он получает квант времени. Только если такого потока нет, система ищет поток с приоритетом 30, если и такого не нашлось, спускается ещё на 1 в иерархии потоков и т.д. Так что поток с низким приоритетом получит квант времени только в том случае, если не нашлось ни одного готового к выполнению потока с более высоким приоритетом. Иначе говоря, поток с низким приоритетом работает только во время простоя потоков с более высоким приоритетом. Но на этом обиды, учиняемые потокам с низким приоритетом, не кончаются. Если потоку выделен квант времени, и, пока он работает, система выяснила, что готов к выполнению поток с более высоким приоритетом, ему немедленно будет предоставлено время. А потоку с низким приоритетом система не даст даже доработать его законный квант времени. Этот поток будет немедленно остановлен. Это и есть вытесняющаяся многозадачность.

Поток создаётся функцией CreateThread:

HANDLE CreateThread(IN LPSECURITY_ATTRIBUTES lpThreadAttributes,
IN SIZE_T dwStackSize,

IN LPTHREAD_START_ROUTINE lpStartAddress,
IN LPVOID lpParameter,
IN DWORD dwCreationFlags,
OUT LPDWORD lpThreadId);

При успешном выполнении функция создаёт новый поток, возвращает его дескриптор, а в переменную, на которую указывает параметр lpThreadId, заносит идентификатор потока. Начиная с Windows 2000 в качестве значения lpThreadId можно задавать NULL, если идентификатор потока вас не интересует.

Выполнение потока начинается с выполнения функции, адрес (имя) которой указывает параметр lpStartAddres. Это любая написанная вами функция, в которую передаётся один аргумент, являющийся указателем на любую величину, и которая возвращает 32-разрядное значение:

DWORD WINAPI имя_функции(LPVOID);

Параметр l pParameter задаёт значение аргумента, передаваемого в функцию, на которую указывает lpStartAddress. Если в функцию не требуется ничего передавать, можно задать lpParameter = NULL.

Остальные параметры функции CreateThread можно во многих случаях задавать нулевыми. Параметр lpThreadAttributes является указателем на структуру типа SECURITY_ATTRIBUTES, определяющую для потока, будет ли наследоваться возвращённый функцией дескриптор дочерними процессами. Эта структура задаёт также дескриптор защиты. Значение параметра lpThreadAttributes можно задать равным NULL. Это будет означать, что дескриптор потока не наследуется и используется дескриптор защиты по умолчанию.

Параметр dwStackSize указывает размер в байтах стека, который будет использоваться в новом потоке. Если в качестве значения dwStackSize задан 0, то размер стека будет таким же, как в первом потоке данного процесса. Стек расположится в памяти процесса и освободит её, когда завершится. Впрочем, если потребуется, то размер стека автоматически увеличится. Так что в большинстве случаев вас вполне устроит значение параметра dwStackSize, равное 0. Если же вы задаёте отличный от нуля размер, надо быть уверенным, что он не превысит размер доступной памяти. Иначе функция CreateThread завершится с ошибкой.

Параметр dwCreationFlags предназначен для флагов, которые определяют условия создания потоков. Но пока определено только одно значение флага - CREATE_SUSPENDED. Если этот флаг не задан, то поток начинает выполняться немедленно после создания. А если задан флаг CREATE_SUSPENDED, то поток создаётся в остановленном, " замороженном " состоянии. Это можно использовать для настройки каких-то характеристик потока. Он начинает выполняться только после того, как к нему будет применена описанная далее функция ResumeThread.

Ниже приведён простейший пример, поясняющий возможную схему создания, выполнения и завершения потока:

DWORD WINAPI ThreadFunc(LPVOID)
{
Form1-> Label1-> Caption = " Поток выполняется";
return 0;
}
void __fastcall TForm1:: Button1Click(TObject *Sender)
{
DWORD lpThreadId;
HANDLE hT = CreateThread(NULL, 0, ThreadFunc, NULL, 0, & lpThreadId);
CloseHandle(hT);
}

В данном примере функция, которая выполняет поток, названа ThreadFunc. Она не использует передаваемый в неё параметр, так что в загаловке функции оставлено только указание типа принимаемого параметра. Функция имеет доступ ко всем глобальным данным первого потока процесса. Например, в данном примере использован вывод сообщения в метку Label1 формы Form1, указатель на которую является глобальной переменной.

Функция Button1Click является обработчиком щелчка на кнопке Button1. Все подобные обработчики относятся к первому потоку приложения. В этой функции создаётся поток с дескриптором hT. В качестве адреса начала потока задаётся функция ThreadFunc. Идентификатор потока, который в данном примере заносится в переменную lpThreadId, нам не нужен. Т.к., начиная с Windows 2000, последний аргумент в вызове функции можно было бы задать равным NULL, и не выделять переменную для хранения идентификатора. В примере предполагается, что дескриптор потока нам далее не потребуется. Поэтому он сразу закрывается функцией CloseHandle.

Если бы нам надо было передать в поток какой-то параметр, это можно было бы сделать следующим образом:

DWORD WINAPI ThreadFunc(LPVOID P)
{
Form1-> Label1-> Caption = " Поток выполняется с параметром " + IntToStr(P);
return 0;
}
void __fastcall TForm1:: Button1Click(TObject *Sender)
{
DWORD lpThreadId;
int lpParameter = 1;
HANDLE hT = CreateThread(NULL, 0, ThreadFunc, & lpParameter, 0, & lpThreadId);
CloseHandle(hT);
}

В приведённом коде выделены жирным шрифтом отличия от предыдущего примера. В данном случае в вункцию передаётся в качестве параметра указатель на целую переменную. Но точно также можно передать указатель на любой тип данных.

Начиная с вызова функции CreateThread, созданный поток начинает выполняться параллельно с основным, первым потоком. В приведённом примере работа созданного потока завершается при выполнении оператора return. передаваемое этим оператором значение является кодом завершения потока. Возможно и иное завершение потока - функцией ExitThread:

VOID ExitThread(IN DWORD dwExitCode);

Параметр dwExitCode задаёт код завершения. Этот код, переданный в функцию ExitThread или возвращённый оператором return, можно затем определить, вызвав функцию GetExitCodeThread:

BOOL GetExitCodeThread(IN HANDLE hThread, OUT LPDWORD lpExitCode);

Параметр lpExitCode является указателем на переменную, в которую будет занесён код завершения потока hThread. Если поток ещё не завершился, в эту переменную будет записано значение STILL_CATIVE. Так что вызов функции GetExitCodeThread может использоваться для проверки того, завершился ли поток с дескриптором hThread, и если завершился, то с каким кодом:

DWORD Code;
GetExitCodeThread(hThread, & Code);
if(Code == STILL_ACTIVE)
...
else
...

Завершить выполнение потока можно также функцией TerminateThread:

BOOL TerminateThread(IN OUT HANDLE hThread, IN DWORD dwExitCode);

Параметр dwExitCode задаёт код завершения. Завершение потока функцией TerminateThread более грубое, чем завершение функцией ExitThread. Его можно использовать только в тех случаях, когда известно, что поток успел завершить все необходимые операции.

Выполняющийся поток можно приостановить с помощью функции SuspendThread:

DWORD SuspendThread(IN HANDLE hThread);

Применять эту функцию надо с некоторой осторожностью. В момент её вызова поток может выполнять операции с каким-то ресурсом, заблокировав его. Например, может динамически выделять память, заблокировав heap. Тогда другие процессы могут воспользоваться этим ресурсом только после того, как поток возобновит работу, т.е. когда для него будет вызвана функция ResumeThread:

DWORD ResumeThread(IN HANDLE hThread);

Эту же функцию следует вызвать, чтобы начал выполняться поток, для которого при его создании функцией CreateThread был создан флаг CREATE_SUSPENDED.

Система ведёт счётчик вызовов функций SuspendThread и ResumeThread для потока. Каждый вызов SuspendThread увеличивает этот счётчик на 1 (но результат не более величины, задаваемой константой MAXIMUM_SUSPEND_COUNT), а каждый вызов ResumeThread уменьшает его на 1 (но результат не менее 0). Обе функции возвращают предыдущее значение этого счётчика. Функция ResumeThread возобновит выполнение потока тольк в том случае, если счётчик стал равен 0. Иначе говоря, если для потока несколько раз была вызвана функция SuspendThread, то выполнение возобновится только после того, как столько же раз будет вызвана функция ResumeThread. Так что если вы хотите наверняка освободить поток, независимо от числа предварительных вызовов SuspendThread, следует выполнить опрератор:

while(ResumeThread(hT) > 0);

В случае ошибки при выполнении функций SuspendThread и ResumeThread они возвращают значение 0xFFFFFFFF. Тогда причину ошибки можно установить, вызвав функцию GetLastError.

Теперь рассмотрим вопросы, связанные с приоритетами потоков. Поток, созданный функцией CreateThread, имеет приоритет, совпадающий с классом приоритета породившего его процесса. В дальнейшем этот приоритет можно изменить, задав отклонение от этого базового приоритета в ту или иную сторону. Это делается функцией SetThreadPriority:

BOOL SetThreadPriority(UN HANDLE hThread, IN int nPriority);

Параметр nPriority указывает требуемое соотношение между классом приоритета процесса и приоритетом потока. Этот параметр может принимать следующие значения:

THREAD_PRIORITY_IDLE Задаёт значение 1 для классов приоритета IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS, и значение 16 для класса приоритета REALTIME_PRIORITY_CLASS.
THREAD_PRIORITY_LOWEST Задаёт значение на 2 ниже нормального класса приоритета.
THREAD_PRIORITY_BELOW_NORMAL Задаёт значение на 1 ниже нормального класса приоритета.
THREAD_PRIORITY_NORMAL Задаёт значение нормального класса приоритета.
THREAD_PRIORITY_ABOVE_NORMAL Задаёт значение на 1 выше нормального класса приоритета.
THREAD_PRIORITY_HIGHEST Задаёт значение на 2 выше нормального класса приоритета.
THREAD_PRIORITY_CRITICAL Задаёт значение 15 для классов приоритета IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS, и значение 31 для класса REALTIME_PRIORITY_CLASS.

Численные значения приведённых в таблице констант соответствуют отклонению от нормального класса приоритета: 1, 2, 0, -1, -2, и т.д.

Поскольку в момент создания поток имеет класс приоритета, заданный для процесса, возникает вопрос создания потока, сразу выполняющегося с другим приоритетом. Это можно сделать, создавая приостановленный поток, т.е. задавая флаг CREATE_SUSPENDED в функции CreateThread. К этому приостановленному потоку можно применять функцию SetThreadPriority, а потом освободить поток функцией ResumeThread. Например:

DWORD dwThreadID;
HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, NULL,
CREATE_SUSPENDED, & lpThreadId);
SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST);
ResumeThread(lpThreadId);
CloseHandle(lpThreadId);

Узнать текущий приоритет потока можно функцией GetThreadPriority:

int GetThreadPriority(IN HANDLE hThread);

Она возвращает численное значение приоритета потока hThread. Эти значения обсуждались выше для функции SetThreadPriority.

В API Windows предусмотрен ряд механизмов, призванных обеспечить синхронизацию выполнения различных приложений и их отдельных потоков. Необходимость синхронизации может быть связана с использованием каких-то неразделяемых ресурсов. Например, несколько приложений хотят записывать или читать данные, используя один и тот же порт. Но одновременно это делать невозможно. Значит, если одно приложение или один его поток захватил ресурс, остальные должны ждать, пока ресурс освободится. Необходимо синхронизировать процессы и потоки также в том случае, если один из них требует результаты, которые должны быть получены другим. Тогда первый из них должен ждать, пока второй получит требуемые данные.

Для синхронизации предусмотрены различные типы синхронизирующих объектов. Это объекты событий (event), обеспечивающие ожидане некоторых событий, мьютексы (mutex), позволяющие только одному из процессов захватывать какие-то неразделяемые ресурсы, таймеры ожидания (waitable timer), обеспечивающие задержки на заданное время. Все эти объекты будут рассмотрены в следующих статьях. В качестве объектов синхронизации могут также выступать такие объекты, создаваемые, прежде всего, для иных целей, как сообщение об изменениях в каталогах (change notification), консольный ввод (console input), процесс (process), поток (thread).

Каждый из объектов синхронизации может быть в одном из двух состояний: сигнальном (signaled) или несигнальном (nosignaled). Для различных типов объектов смысл этих двух состояний несколько различается. В приведённой ниже таблице №1 даются краткие предварительные сведения об объектах синхронизации, управляющих ими функциях и смысле сигнального состояния.

Таблица №1 - синхронизирующие объекты

сообщение об изменении (change notification) Дескриптор этого объекта возвращается функцией FindFirstChangeNotification. Объект переходит в сигнальное состояние, когда в указанном каталоге произошло событие указанного типа.
консольный ввод (console input) Дескриптор этого объекта фозвращается функцией CreateFile со спецификатором CONIN$, или функцией GetStdHandle. Объект находится в сигнальном состоянии, если во входном буфере консольного ввода есть непрочитанные данные, и переходит в несигнальное состояние, если буфер пуст.
событие (event) Дескриптор этого объекта возвращается функциями CreateEventили OpenEvent. Объект переводится в сигнальное состояние функциями SetEvent и PulseEvent. В несигнальное состояние может переводится функцией ResetEvent.
мьютекс (mutex) Дескриптор этого объекта возвращается функциями CreateMutex или OpenMutex. Объект находится в сигнальном состоянии, если в данный момент у него нет владельца.
семафор (semaphore) Дескриптор этого объекта возвращается функциями CreateSemaphore или OpenSemaphore. Объект находится в сигнальном состоянии, если число использующих его процессов больше 0.
процесс (process) Дескриптор этого объекта возвращается функциями CreateProcess или OpenProcess. Объект переходит в сигнальное состояние, когда процесс завершается.
поток (thread) Дескриптор этого объекта возвращается функциями CreateProcess, CreateThread, CreateRemoteThread. Объект переходит в сигнальное состояние, кагда поток завершается.
таймер ожидание (waitable timer) Дескриптор этого объекта возвращается функциями CreateWaitableTimer или OpenWaitableTimer. Объект активируется функцией SetWaitableTimer и деактивируется функцией CancelWaitableTimer. Переходит в сигнальное состояние, когда истекает заданный отрезок времени.

В объектах синхронизации имеются счётчики числа обращений к ним. Если объект был N раз подряд переведён в несигнальное состояние, то только после N переводов в сигнальное состояние объект действительно перейдёт в это состояние.

Удаление созданного объекта синхронизации может осуществляться функцией CloseHandle:

BOOL CloseHandle(IN OUT HANDLE hObject);

где hObject - дескриптор объекта. Функция возвращает ненулевое значение при успешном завершении.

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

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

Создайте потомка класса TThread, чтобы представить выполняемый поток в многопоточном приложении. Каждый новый экземпляр потомка TThread - новый поток выполнения.

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

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

 

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

есть и другие менее используемые свойства и методы.

Для того, чтобы создать новый объект класса, производный от 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 поток выполняется только, когда система простаивает. Другие потоки, не будут прерываны, чтобы выполнить поток с 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 метод для следующих объектов:

  • Компоненты Data access потокобезопасны следующие: Для BDE-доступных наборов данных, каждый поток должен иметь собственный компонент сеанса базы данных. Одно исключение к этому - то, когда используются драйверы Microsoft Access, которые сформированы, используя библиотеку Microsoft, которая не потокобезопасна. Для dbDirect, пока библиотека клиента потокобезопасна, dbDirect компоненты будут потокобезопасны. ADO и InterbaseExpress компоненты потокобезопасны. При использовании в потоках компонентов data access нужно быть внимательней. Таким образом, например, нужно вызывыть Synchronize, которые связывают data control с dataset, устанавливая свойство DataSet объекта data source, но не следует использовать Synchronize, чтобы обратиться к данным field в dataset.
  • DataCLX объекты потокобезопасны, хотя объекты VisualCLX - нет.
  • Graphics объекты потокобезопасны. Не следует использовать главный VCL или CLX поток, чтобы обратиться к TFont, TPen, TBrush, TBitmap, TMetafile (VCL только), TDrawing (CLX только), или TIcon. Объекты Canvas могут использоваться вне Synchronize метода, необходимо только блокировать их перед применением и освобождать после,
    для этого используются методы Lock() и Unlock(), например, так:
    //---
    Form1-> Canvas-> Lock();
    for(int i=0; i< 5000; i++) Form1-> Canvas-> TextOut(20, 20, i);
    Form1-> Canvas-> Unlock();
    //---
  • В то время как объекты списка list не потокобезопасны, можно использовать потокобезопасную версию, TThreadList, вместо TList.

Поделиться с друзьями:

mylektsii.su - Мои Лекции - 2015-2024 год. (0.014 сек.)Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав Пожаловаться на материал