Отправляет email-рассылки с помощью сервиса Sendsay

Borland C++ Builder - всякая всячина

  Все выпуски  

Borland C++ Builder всякая всячина (нити, потоки, класс TThread)


Служба Рассылок Subscribe.Ru проекта Citycat.Ru

Приветствую всех получателей рассылки Borland C++ Builder - всякая всячина!

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

Нашел я еще одну интересную темку для душевной беседы. Очень часто бывает необходимо, чтобы определенные действия в программе производились параллельно с основными вычислениями и не мешали нормальному управлению всей системой. Образцовый пример таких действий - сама операционная система Windows, позволяющая выполнять в один момент времени несколько задач и, хотя, параллельность выполнения несколько притянута за уши (процессор-то все-таки один), создается впечатление, что программы выполняются именно одновременно. Это достигается путем быстрого переключения между задачами, незаметного нашему глазу, или, как говорят умные дяди, выделением задачам определенного кванта процессорного времени.

Однако, если разбираться в системе более глубоко, окажется, что так называемое квантование имеет не только визуальные достоинства, радующие глаз пользователя. Например, операционные системы семейства NT/2000, установленные на компьютеры, имеющие более одного процессора, могут реально разделять выполнение нескольких задач между имеющимися процессорами.
На самом деле, "распараллеливаются" в ОС не сами задачи, как это может показаться с первого взгляда, а так называемые нити (Thread), которых в программе может быть сколько угодно, причем сама программа при своей загрузке "регистрируется" в системе именно как нить и может, соответственно, быть выполнена в отдельном потоке.
Кроме того, как я уже сказал, в своей программе вы можете создать еще несколько нитей, которые будут выполняться "независимо по времени" от основной части программы, а на многопроцессорной системе даже смогут получить в личное пользование отдельный процессор...

Теперь о том, как это делается. Есть два способа:

  1. через функции Win API CreateThread ... TerminateThread и т.д. создается нить, запускается, управляется, "убивается". Вариант не для слабонервных, подходит, как я думаю, только в том случае, если вы серьезно ограничены в ресурсах (приложение должно работать исключительно быстро и занимать как можно меньше памяти), применяется в основном для создания консольных приложений, сервисов и серверов, игр и т.д.
  2. Через абстрактный класс библиотеки VCL TThread, который уже содержит все основные управляющие элементы, выполняющие море рутинной работы. Именно его я и хочу сегодня рассмотреть более подробно.

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

Давайте, однако, определимся с задачей. Сделаем, например, форму, на которой будет располагаться метка (TLabel) с прокручивающимся по горизонтали заголовком. Задача эта практического значения, в общем-то, не имеет, но позволяет лучше уловить сам смысл теории параллельных процессов.
Итак, создайте новый проект, на его главную формочку положите метку TLabel, свойству Caption этой метки присвойте в Object Inspector какое-нибудь значение, например, строчку "Главное, чтобы костюмчик сидел...". И все...

Теперь перейдем к программой части. Откройте файл заголовка, в нем, перед объявлением класса главной формы TForm1 создайте объявление класса-наследника от TThread (кстати, все его свойства и методы очень хорошо описаны в хелпе, чтением которого ни в коем случае не стоит пренебрегать. Очень советую выделить TThread в файле заголовка и нажать F1):

//---------------------------------------------------------------------------
 
class TCaptionThread: public TThread
{
private:
    TLabel *FLabel;
protected:
    void __fastcall Execute(void);
public:
    __fastcall TCaptionThread(void);
    __property TLabel *Label= { read= FLabel, write= FLabel };
};
//---------------------------------------------------------------------------

Итак, что же новенького мы добавили:

  1. Свойство Label, которое будет указывать на метку Label1 в главной форме
  2. Переопределили метод Execute, который как раз и предназначен для выполнения каких-либо действий в нашей нити.
  3. Перекрыли конструктор класса TThread.

Теперь в содуль "Unit1.cpp" добавьте следующий код:

//---------------------------------------------------------------------------
 
__fastcall TCaptionThread::TCaptionThread(void)
     : TThread(true)
{
    Label= NULL;    // Данное присвоение я сделал на тот случай, если компилятор сам не сделает инициализации данного свойства, что очень возможно при компиляции с использованием оптимизации
}
//---------------------------------------------------------------------------
 
void __fastcall TCaptionThread::Execute(void)
{
    while (!Terminated)    // Не было попытки остановить вычисление в данной нити
    {
      if (Label!=NULL)    // Свойство Label указывает на реальный объект главной формы
      {
        AnsiString Str= Label->Caption;
        Str= Str.SubString(Str.Length(), 1) +
             Str.SubString(1, Str.Length() - 1);
        Label->Caption= Str;
      }
      Sleep(100);    // "Отдаем" системе 100 милисекунд процессорного времени
    }
}
//---------------------------------------------------------------------------

Как вы могли заметить, конструктор TCaptionThread вызывает конструктор своего родителя TThread с аргументом true, хотя сам не имеет никаких аргументов. Дело в том, что конструктор TThread с помощью параметра bool CreateSuspended, создает "приостановленную" нить (true) или может сразу запустить ее на выполнение (false). Мы же создаем всегда "приостановленную" нить для того, чтобы ей можно было передать некоторые параметры и только после этого начать вычисления.
После того, как нить "получает разрешение" начать вычисления, вызывается ее метод Execute, в котором вы можете делать все, что вам заблагорассудится, естественно, соблюдая приличия, а именно: если вы делаете какие-то вычисления в цикле и они могут длиться неопределенное время, вы должны позаботиться о реакции нити на требование прекратить работу, что у нас и проверяется в операторе while (!Terminated). Это свойство устанавливается в true при вызове метода TThread::Terminate(), и в этом случае вы должны завершить выполнение метода Execute.
Кроме того, если вы реализуете какой-нибудь фоновый процесс, который не должен отнимать драгоценные ресурсы процессора у других нитей, вы можете принудительно отдавать системе часть процессорного времени, используя функцию Sleep(100), где 100 - отдаваемое время в милисекундах (этот параметр подбирается опытным путем).

Все остальное в нашем методе Execute - это напосредственно рабочий код. Вначале мы проверяем указатель на метку, если он ссылается на реальный объект, присваиваем содержимое заголовка этой метки вспомогательной переменной Str, переносим в ней последний символ в начало строки и присваиваем полученный результат вышеназванному заголовку. И так до бесконечности, пока метод Execute не получит требования остановиться.

Нам осталось только создать код для главной формы, управляющий созданием, работой, и остановкой нити. В Object Inspector на вкладке Events создайте для главной формы обработчики событий OnActivate и OnClose. Нарисуйте для них следующий код:

//---------------------------------------------------------------------------
 
void __fastcall TForm1::FormActivate(TObject *Sender)
{
    Thread= new TCaptionThread();
    Thread->FreeOnTerminate= true;
    Thread->Priority= tpIdle;
    Thread->Label= Label1;
    Thread->Resume();
}
//---------------------------------------------------------------------------
 
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
    Thread->Terminate();
    Thread->WaitFor();
}
//---------------------------------------------------------------------------

Рассмотрим метод FormActivate, который вызывается при выводе формы на экран. В нем мы, во-первых, создаем нашу нить, присваеваем ее свойству FreeOnTerminate значение true, которое, в общем-то, и так по умолчанию равняется true, но, как говорится, "береженого - бог бережет", да и нагляднее получается. При установке этого свойства в true, объект нити будет разрушен сразу по окончании выполнения метода Execute, что избавляет нас от необходимости следить за его судьбой. Затем мы определяем, с каким приоритетом будет выполняться наша нить, то есть, в каких пропорциях будет делиться процессорное время между ней и остальными потоками (нитями, процессами, программами). Свойство Priority может принимать следующие значения:

  • tpIdle - Нить получает "время", только если система (Windows) "простаивает"
  • tpLowest - Приоритет на две "ступени" ниже обычного приоритета
  • tpLower - Приоритет на одну "ступень" ниже обычного приоритета
  • tpNormal - Нормальный приоритет (обычный для большинства задач)
  • tpHigher - Приоритет на одну "ступень" выше обычного приоритета
  • tpHighest - Приоритет на две "ступени" выше обычного приоритета
  • tpTimeCritical - нить получает высший приоритет (используется только для создания систем "реального времени"

Мы не только устанавливаем приоритет в tpIdle, но и, как вы помните, отдаем системе еще 100 милисекунд при каждой итерации цикла. Если вам покажется, что вашу нить "ущемляют в правах", вы всегда можете повысить ее приоритет, но делайте это очень осторожно, чтобы это не вызвало "торможение" главной нити программы.

После этого мы присваеваем свойству нити Label адрес метки Label1 и отпускаем нить в "свободное плавание" вызовом метода Resume() (вы можете приостановить работу нити вызовом метода Suspend() и возобновить ее работу методом Resume()).

При закрытии формы мы, с помощью вызова метода Terminate(), "приказываем" нити завершить ее работу и вызываем метод WaitFor(), который "ждет" завершения работы метода Execute(). Поскольку у нас установлено свойство FreeOnTerminate, сразу после завершения выполнения метода WaitFor() объект нити будет разрушен.

Вот и все. Откомпилируйте пример и посмотрите на результат. Для более плавной прокрутки советую установить для метки шрифт фиксированной ширины, например, Courier New, тогда не будет заметно разницы между сдвигом букв и сдвигом знаков препинания в тексте.

Что еще можно сказать? Мы оставили без внимания очень важное свойство нитей - синхронизацию свойе работы с другими потоками, но этот вопрос очень не простой и достоин персонального обсуждения, что, может быть, я скоро и сделаю.



С уважением, Васильев Евгений...

http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу
Рейтингуется SpyLog

В избранное