
- •Тема 6. Особенности разработки пакетов прикладных программ
- •Определение ппп и его составные части
- •. 2.16. Вариант классификации ппп по функциональному назначению
- •Иными словами, функциональная связь в модели предметной области представляется:
- •Способы применения ппп и задача управления процессами
- •Способы динамического запуска вычислительных процессов
- •. Рис. 2.18. Запуск внешней программы с замещением родительского процесса
- •Например, оператор
- •If (execlp("f1.Ехе","Fl.Exe", null)) ShowMessage ("Программа Fl.Exe не выполнена");
- •Реализация динамического обмена данными методом dde
- •Таким образом, взаимодействие dde может выполнять следующие операции:
- •Во время же выполнения программы эти свойства могут быть установлены методом SetLink, который описывается следующим образом:
- •Основы архитектуры сом
- •Основы автоматизации ole
- •Элементы управления ActiveX
- •Организация приложений с несколькими потоками
- •Заголовочный файл Unit2.H:
Организация приложений с несколькими потоками
Как известно [Cb], в общем случае процесс (выполняемая программа) может включать в себя не один, а несколько последовательностей команд (потоков или так называемых нитей - tread), на которые операционная система выделяет определенные кванты времени. Поэтому потоки выполняются как бы параллельно. Если в приложении имеются независимые друг от друга задачи (хотя бы частично зависимые), выполнение которых требует достаточно длительного времени, целесообразно для каждой из этих задач выделить свой поток или нить.
Например, одна нить выполнения может осуществлять основную работу, а вторая, с меньшим приоритетом, может в то же время готовить или реорганизовывать какие-то файлы, рисовать изображения, которые потребуются в дальнейшем, т.е. выполнять черновую работу. Другой пример — параллельная работа с несколькими внешними источниками информации.
Особенно большой выигрыш в производительности за счет параллельного выполнения нескольких нитей можно получить в многопроцессорных системах, в которых можно организовать выполнение каждой нити отдельным процессором.
Параллельно выполняемые нити работают в адресном пространстве одного процесса и могут иметь доступ к глобальным переменным этого процесса. Одним из способов создания приложения с несколькими потоками является использования компонент типа TThread. Этот компонент отсутствует в палитре библиотеки. TThread — это абстрактный класс, позволяющий создать в приложение, отдельную нить выполнения. Для того чтобы ввести TThread в свое приложение надо ваполнить команду «File | New Other» и в открывшемся окне Депозитария на странице New выбрать пиктограмму Thread Object. Вам будет задан вопрос об имени (Class Name) создаваемого класса, наследующего TThread. Укажите любое имя (например Т) и в ваш проект добавится новый модуль, файлы которого имеют вид:
Заголовочный файл Unit2.H:
#ifndef Unit2H #define Unit2H //-------------------------------------------- #include class T: public TThread { private: /* Добавляемые здесь и в разделе protected свойства и методы будут доступны только внутри класса */ protected: void _fastcall Execute(); public: _fastcall T(bool CreateSuspended); /* Добавляемые здесь свойства и методы будут доступны для функций приложения через объект нити */ #endif файл реализации Unit2.cpp: #include #pragma hdrstop #include "Unit2.h" #pragma package(smart_init) //-------------------------- /* Важно: методы и свойства объектов VCL могут использоваться только с применением метода, который вызывается методом Synchro-nize, например: Synchronize(UpdateCaption); где метод UpdateCaption может иметь вид: void __fastcall Unit.2::UpdateCaption() { Forml->Caption = "Updated in a thread"; } */ _fastcall T::T(bool CreateSuspended) : TThread (CreateSuspended) { } //------------------------------------ void _ fastcall T::Execute() { // Здесь размещается код потока }
Созданный C++Builder модуль, как вы можете видеть, содержит заготовку классов с введенным вами именем (в нашем пример — Т), наследующего TThread. Вы можете добавлять в него любые свойства и методы, учитывая отмеченные в комментариях области видимости. Процедура Execute, заготовку которой вы можите видеть в коде, является основной процедурой нити. При ее окончании завершается и выполнение данной нити приложения.
Класс наследует от TThread ряд методов. Прежде всего, это конструктор, создающий объект нити:
_fastcall TThread(bool CreateSuspended);
Параметр CreateSuspended конструктора определяет способ выполнения нити. Если CreateSuspended = false, то выполнение процедуры Execute начинается немедленно после создания объекта. Если CreateSuspended = true, то выполнение начинается только после того, как будет вызван метод Resume:
vojd _fastcall Resume (void);
Конструктор TThread не должен вызываться в приложении явным образом.
Для создания объекта класса TThread, как и для всех классов VCL, надо использовать операцию new. Например:
T *SecondProcess = new Т(true); SecondProcess->Resume();
Функция Resume имеет две области применения. Во-первых, она запускает выполнение, если объект нити был создан с CreateSuspended = true. Во-вторых, она запускает приложение, приостановленное ранее методом Suspend:
void _fastcall Suspend(void);
Таким образом, вы можете в любой момент приостановить выполнение нити методом Suspend, а затем продолжить выполнение методом Resume. Если вы подряд несколько раз вызвали метод Suspend, то выполнение возобновится только после того, как столько же раз будет вызван метод Resume. Узнать, является ли нить приостановленной, можно по значению булева свойства Sus-pended.
В функции Execute можно непосредственно писать операторы выполнения, вызовы каких-то иных функций и т.п. Однако если функция должна вызывать какие-то методы компонентов VCL или обращаться к свойствам формы, то необходимо соблюдать осторожность, поскольку при этом не исключены конфликты между параллельно выполняемыми нитями. В этом случае в функции надо вызывать метод Synchronize, как сказано в комментарии, который вы могли видеть в приведенной выше заготовке модуля. Метод определен следующим образом:
Typedef void _fastcall (_closure *TThreadMethod) (void); void _fastcall Synchronize(TThreadMethod &Method);
В этом определении Method — функция, работающая с компонентами VCL. Таким образом, при работе с компонентами VCL надежнее строить выполнение следующим образом. Вы пишете функцию, выполняющую необходимые действия с компонентами VCL.
Пусть вы дали ей имя Work. Тогда вы включаете ее объявление в класс нити, например, в раздел private, даете в файле реализации ее описание, а процедура Execute в этом случае может, например, состоять из единственного оператора Synchronize(Work):
заголовочный файл Unit 2.h:
... {class T: public TThread { private: void _fastcall Work(void); protected: void _fastcall Execute(); public: _fastcali Т(bool CreateSuspended); };
файл реализации Unit2.cpp:
... void _fastcall T::Execute() { Synchronize(Work); } //-------------------------------------- void _fastcall T::Work(void) { ... }
Нормальное завершение выполнения нити происходит при завершении процедуры Execute. Однако возможно и досрочное завершение выполнения нити. Для этого в ее процедуру Execute должна быть введена проверка булева свойства Terminated (завершено). Нормально это свойство равно false. Но если какая-то внешняя нить вызвала метод Terminate объекта данной нити, то Terminated становится равным true. Если предполагается возможность такого завершения выполнения нити, то процедура Execute должна периодически проверять значение Terminate и при получении значения true должна завершаться. Например:
void _fastcall T::Execute() { do { ... } while (!Terminated); }
Метод Terminate обеспечивает «мягкое» завершение нити. Процедура Execute сама решает, в какой момент ей удобно завершить выполнение. Имеется и более грубая функция API Windows — TerminateThread, вызывающая немедленное завершение выполнения нити. Например, оператор
TerminateThread ((void *) SecondProcess->Handle,0);
прервет выполнение объекта нити с именем SecondProcess. В этом операторе использовано свойство Handle (дескриптор) нити, позволяющее обращаться к функциям API Windows. Второй параметр функции TerminateThread (в приведенном примере 0) задает код завершения нити. Этот код можно прочитать в свойстве ReturnValue объекта нити.
Что именно происходит при завершении выполнения нити, определяется свойством FreeOnTerminate объекта типа TThread:
_property bool FreeOnTerminate,
Так, если FreeOnTerminate = true, то при завершении выполнения объект TThread разрушается, освобождая память. При FreeOnTerminate = false освобождать память надо явным применением к объекту операции delete.
Если одновременно выполняется несколько нитей, то может потребовать чтобы какая-то нить ожидала завершения другой нити, которая, например, готовит ей исходные данные. Такое ожидание можно осуществить, применив к нити, завершения которой надо ждать, метод WaitFor:
int _fastcall WaitFor(void);
Этот метод возвращает свойство ReturnValue — код завершения ожидаемой нити. Метод не возвращается, пока нить, которую ожидают, не завершит выполнения. Каждая выполняемая нить имеет определенный уровень приоритета, определяемый значением ее свойства Priority. Это свойство может иметь следующие значения:
Priority |
Значение |
tpIdle |
Нить выполняется только когда система свободна. |
tpLowest |
Приоритет нити на два пункта ниже нормального. |
tpLower |
Приоритет нити на один пункт ниже нормального. |
tpNormal |
Нормальный приоритет нити |
tpHigher |
Приоритет нити на один пункт выше нормального. |
tpHigherst |
Приоритет нити на два пункта выше нормального. |
tnTimeCritical |
Нить имеет наивысший приоритет. |