Николай дата публикации 01-07-2003 19:22 Три компонента для параллельного программирования (THProcess, THChannel, THMutex).
Этот невизуальный компонент - порождение класса TThread. Он позволяет создавать параллельно выполняющиеся процессы внутри одного приложения. А обработчиками событий компонента можно "укусить" поток команд с разных сторон, что и превращает поток в контролируемый процесс.
В компоненте обрабатываются восемь событий. Четыре процедуры обработки выполняются в том потоке команд, в котором вызываются, остальные выполняются VCL-потоком. В VCL-потоке выполняются процедуры обработки событий: OnDraw, OnException, OnTerminate и OnTrace.
Событие OnTerminate происходит когда процесс завершается нормально(сам) или принудительно, из другого процесса методом TerminateHard. OnException - при исключительной ситуации.
События OnDraw и OnTrace происходят при обращении процесса к методам Draw и Trace, соответственно. Эти методы выполняются VCL-потоком и предназначены для вывода информации из процесса на экран (обновления/изменения свойств VCL-компонентов).
В процедуре обработки события OnExecute программируется сам алгоритм процесса.
События OnSuspend и OnResume происходят перед тем, как процесс приостанавливается либо активизируется. Событие OnStart происходит перед запуском потока команд процесса. В этих процедурах нельзя изменять визуальные компоненты формы так, как VCL-компоненты не являются реентерабельными, то есть попытка одновременного обращения к VCL-компоненту из двух и более потоков приведет к разрушению данных компонента и, как правило, к аварийному завершению программы.
Всем процедурам обработки событий первым или единственным параметром передается HP класса THProcess - объект процесса, в котором произошло событие. Это позволяет программе обработки события получить доступ к свойствам и методам процесса.
Поле Params, бестиповой указатель, служит для передачи параметров процессу и обработчикам событий.
Для динамического создания процессов предназначен метод CreateNew, который создает новый экземпляр THProcess. Для идентификации процесса можно использовать числовые поля ThreadID, ProcessID и строковое поле TraceName. Поля ProcessID и TraceName можно устанавливать самостоятельно.
CreateNew создает процесс приостановленным. Активизируется созданный процесс методами Resume или Execute, когда все его переменные свойства сформированы либо модифицированы.
Создание компонента в конструкторе формы, а так же методом CreateNew, еще не означает создание потока команд в понятиях Windows. Поток может быть активизирован сразу после старта приложения, если свойство Suspended установлено в False, иначе - при обращении к методам Execute и Resume. Завершение потока происходит при завершении его программы, либо принудительно из другого потока. Но компонент при этом не разрушается(можно разрушить деструктором) и процедуру OnExecute можно выполнять повторно. Для этого и предназначены методы Execute и Resume. Отличие Resume и Execute только в вызове обработчиков событий(OnStart для Execute и OnResume для Resume).
Все процессы автоматически уничтожаются в деструкторе формы (либо явно процедурой Destroy). Это важно учитывать при завершении приложения, так как потоки процессов могут быть активны. Если дело дошло до деструктора формы, а поток процесса не завершил работу, то его уже ни что не спасет и результат его работы будет не предсказуем, как и любой прерванной программы.
Компонент предназначен для приема и передачи данных из одного процесса в другой. В этом компоненте нет ни свойств ни методов. Он только предоставляет процессам две очереди( чтения и записи) и защиту этих очередей от разрушения.
Создать и запустить несколько потоков в приложении очень просто, сложно обеспечить координацию их действий. Канал обеспечивает процессам точки соприкосновения и соответственно механизм синхронизации.
Если образно, то это такая дырка в стене, в которую можно засунуть руку и передать "что-то" на другую сторону. Если тех, кто хочет передать много, то первым будет тот, чья рука успела раньше. Остальные будут ждать, когда канал освободится.
С другой стороны стены, должны быть желающие это "что-то" получить. Они также засовывают руку в дырку (канал) по очереди, поскольку пролезает только одна рука. Ожидание приема-передачи может быть ограничено таймаутом. В таком случае тот, кто хотел "что-то" передать либо получить из канала, освободит канал по истечении времени (уйдет не дождавшись).
В текущий момент времени процесс может засунуть в канал только одну руку, но никак ни две. Блок управления событиями у каждого процесса только один и следовательно, только одна точка ожидания события. Чтобы гарантировать процессы от взаимных блокировок(состояние когда задачи приостанавливаются в ожидании выполнения друг друга) просто нужно использовать в процессе не более одного канала для получения данных и одного канала для передачи. Но состояние бесконечного ожидания при передаче информации из процесса в процесс возможно. Это случается когда один из процессов - участников обмена завершится по какой-либо причине. Эта задача решается в программах-обработчиках событий завершения процесса OnTerminate и OnException. У процесса есть для этого метод Post. Этим методом можно "прочистить" канал, по которому завершенный процесс общался с другими. После команды Post( канал ) все процессы ожидающие событие канала получат уведомление в переменной WaitResult равное wrAbandoned.
Процессы посылают данные в канал метом WriteData, а получают методами ReadData и LoadData. В методах WriteData и ReadData обязательным параметром является длина передаваемых/получаемых данных. Т. е. те, кто обмениваются "чем-то", должны знать, чем именно. В случае использования метода LoadData процесс может не знать, что конкретно ему передали и сам анализировать то, что он получил из канала (адрес информации находится у него в поле DataAddr, длина в DataLength. Эта область памяти выделена передающим процессом и может быть освобождена процедурой FreeData, когда будет не нужна). И если в процессе анализа выяснится, что информация не нужна ее можно отправить в другой, или тот же канал либо выбросить. Метод LoadData актуален, когда принимающий процесс не знает, например, длину данных.
THMonitor очень похож на монитор .NET Framework, а реализован, как критическая секция Windows. Он предоставляет процессам последовательно используемый модуль. То есть THMonitor обеспечивает доступ к своему обработчику события OnEnter нескольким процессам в режиме взаимоисключения. Если в процессе установлено свойство Monitor, то событие монитора OnEnter из процесса вызывается обращением к методу - MonitorEnter, захватить вход в модуль монитора можно также методами Lock и Unlock.
Необходимость возникает, в основном, когда одному процессу нужно узнать о том, что в это время делают другие процессы. Процесс может обрабатывать общую область памяти, в которую записаны данные о состояниях других процессов, и учитывать это, при необходимости(см. пример).
P.S.
Исходный текст компонентов прилагается и его можно исправлять по своему усмотрению. Обнаруженные ошибки, описание программных сбоев, рекомендации и пожелания просьба присылать автору.
К материалу прилагаются файлы:
[Потоки (нити) Threads]
Обсуждение материала [ 13-08-2004 15:47 ] 7 сообщений |