Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по СПО2.doc
Скачиваний:
45
Добавлен:
02.05.2014
Размер:
515.07 Кб
Скачать

В г Васильев Лабораторная работа 3

по курсу "Системное программное обеспечение ЭВМ»

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

Работа рассчитана на 4 часа (2 занятия)

Цель работы: разработка многопоточных приложений в среде C++Builder.

Задание:

На форме поместить 3 одинаковых изображения. Создать три потока. Каждый поток должен показать, соответственно, красный, зеленый и синий каналы изображения (разложить на RGB- каналы). В программе предусмотреть возможность задания уровня приоритета потока. Во время обработки потоком изображения показывать % выполнения задания. Найти время, затраченное на выполнение задания каждым потоком.

Пример формы

Основные теоретические сведения

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

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

  •  Компоненты Data access потоко-безопасны следующие: Для BDE-доступных наборов данных, каждый поток должен иметь собственный компонент сеанса базы данных. Одно исключение к этому - то, когда Вы используете драйверы Microsoft Access, которые сформированы, используя библиотеку Microsoft, которая не потоко-безопасна. Для dbDirect, пока библиотека клиента потоко-безопасна, dbDirect компоненты будут потоко-безопасны.ADO и InterbaseExpress компоненты потоко-безопасны. При использовании в потоках компонентов data access, Вы должны быть внимательней. Таким образом, например, Вы должны вызывыть Synchronize, которые связывают data control с dataset , устанавливая свойство DataSet объекта data source, но Вы не должны использовать Synchronize, чтобы обратиться к данным field в dataset. Для подробной информации об использовании сеансов базы данных с потоками в приложениях BDE-enabled, см. Managing multiple sessions в Help к C++Builder.

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

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

Помимо координации работы потоков с помощью приоритетов потоков в приложении также часто бывает необходимым синхронизировать потоки. Что при этом имеется ввиду? Синхронизация совместной работы нескольких потоков означает разграничение времени доступа к кому-либо ресурсу (форме, глобальным данным и т.д). Для этого используются такие объекты, как критические разделы (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(), и др.