Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
SysSoft.doc
Скачиваний:
520
Добавлен:
16.03.2016
Размер:
4.36 Mб
Скачать

Пример создания многозадачного приложения с помощью системы программированияBorlandDelphi

Рассмотрим пример использования механизмов, специально созданных для ор­ганизации взаимного исключения, которые имеются в системе программирования BorlandDelphi3.0. Эта система программирования, будучи ориентирован­ной на создание приложений в многозадачной операционной системеMicrosoftWindows9x, содержит в себе стандартные классы, позволяющие без особых уси­лий использовать многопоточные возможности этих ОС. Воспользуемся стан­дартным классомTThread. Объект, создаваемый на основе этого класса, можно охарактеризовать следующими теперь уже очевидными для нас свойствами:

 каждый тред имеет свою, при необходимости уникальную, исполняемую часть;

 каждый тред для своего исполнения требует отдельного процессорного времени, то есть диспетчер задач принимает во внимание только приоритет треда;

 диспетчеризация выполнения тредов осуществляется операционной системой и не требует вмешательства программиста;

 несколько тредов, принадлежащих одному процессу, могут использовать один и тот же ресурс (например, глобальную переменную). Как нам известно, треды могут обращаться к полям другого треда из того же вычислительного процесса. При этом программисту необходимо самостоятельно ограничивать доступ к этому ресурсу во избежание известных проблем.

Итак, пусть необходимо создать многопоточное приложение, схема взаимодейст­вия отдельных потоков в котором (в рамках единого вычислительного процесса) приведена на рис. 6.6.

Рис. 6.6.Схема №1 взаимодействия параллельно выполняющихся задач

Так, согласно этому рисунку, процесс А после своего завершения запускает зада­чи D, С и Е. Считаем, что задачи В, D и С завершаются примерно в одинаковое время. По крайней мере, нам не известно, какой из потоков должен быть первым, а какой – последним. Однако по условиям задачи пусть потокFбудет запус­каться тем из перечисленных тредов, который завершается первым, но только после того, как завершатся два оставшихся треда, приходящие в «точку синхро­низации». Наконец, пусть задачаGзапускается последним закончившим работу потоком Е или F.

Все указанные задачи создадим как потомки объекта TThread. Тексты всех про­граммных модулей приведены в приложении А. Поскольку, согласно условию, действия, выполняемые задачами, для нас не имеют значения, представим исполняемую часть простейшим циклом с соответствующей задержкой. Для нагляд­ности внутри цикла можно организовать вывод текущего состояния выполнения задачи в процентах на «строке состояния», для чего используем компонентTGauge. Благодаря тому, что все семь тредов похожи (используют одни и те же методы) и отличаются только в части принятия решения о синхронизации, опишем орга­низацию базового объекта-треда.

Базовый объект (TTreadProgress) является потомком объектаTTread. При этом он имеет следующие поля:

 имя треда;

 строка состояния треда;

 «длина» треда (время его работы в отсутствие конкурентов);

 текущее состояние треда;

 признак завершения треда;

 имя запустившего треда;

 строка для вывода сообщений в компонент TMemo.

В базовом объекте объявлены следующие процедуры:

 исполняемая часть;

 завершающая часть;

 процедура прорисовки строки состояния;

 процедура вывода сообщения;

 конструктор объекта.

Все треды (от А до G) являются потомками этого объекта и перекрывают един­ственный метод – процедуру завершения процесса. В исполняемой части задачи после завершения цикла задержки, имитирующего выполнение полезной работы, устанавливается признак завершения и вызывается процедура завершения задачи, которая и выполняет соответствующие действия.

Общую схему работы программы, реализующей задание, можно описать следую­щим образом. Все задачи инициализируются соответствующей процедурой одновременно, но в режиме ожидания запуска. В качестве параметров инициализа­ции в создаваемый поток передаются его имя, длительность и имя запускающего объекта (если оно известно заранее). Сразу после инициализации запускаются задачи А и В. Обе задачи сигнализируют об этом соответствующим сообщением. После своего завершения поток А запускает задачи (потоки) С, Dи Е. Далее всё идет в соответствии с заданной блок-схемой. Задача, запускающая другую зада­чу, передаёт ей свое имя, обращаясь непосредственно к полю этого объекта. Ин­формацию о том, завершился тот или иной поток, можно получить, обратившись к соответствующему полю – признаку завершения задачи.

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

Каждый процесс имеет связь с так называемыми VCL-объектами – видимыми компонентами. В данном случае такими являются строка состояния TGaugeи поле сообщенийTMemo. Для того чтобы в процессе работы нескольких парал­лельно выполняющихся задач не возникало критических ситуаций с выводом информации на эти видимые на экране объекты, к ним необходимо обеспечить синхронизированный доступ. Это довольно легко достигается с помощью стан­дартного для объектаTThreadметодаSynchronize. Метод имеет в качестве пара­метра имя процедуры, в которой производится вывод на VCL-объекты. При этом сама эта процедура нигде в программе не должна вызываться напрямую без ис­пользования методаSynchronize. В нашей программе такими процедурами явля­ются прорисовка строки состояния (DoVisualProgress) и вывод текстового сообщения (WriteToMemo). Подобное использование методаSynchronizeобес­печивает корректную работу нескольких параллельных процессов с VCL-объектами.

Однако метод Synchronizeне помогает в случае совместного доступа к другим общим ресурсам. Поэтому необходимо применять другие средства для организации взаимного исключения. Главная цель этих средств заключается в обеспече­нии монопольного доступа для каждой задачи к общим ресурсам, то есть пока один поток не закончил обращение к подобному ресурсу, другой не имеет право этот ресурс использовать.

В системе программирования Delphiдля этой цели имеется довольно-таки про­стой в использовании и достаточно эффективный метод критической секции с помощью объектаTCriticalSection. Этот метод заключается в следующем:

 участок кода каждого потока, в котором производится обращение к общему ресурсу, заключается в «скобки» критической секции – используются мето­ды EnterиLeave;

 если какой-либо тред уже находится внутри критической секции, то другой поток, который дошел до «открывающей скобки» Enter, не имеет права вхо­дить в критическую секцию до тех пор, пока первый поток находится в ней. Когда первый тред выйдет из критической секции, второй сможет войти в неё и, в свою очередь, обратиться к критическому ресурсу.

Очевидно, что такой метод надежно обеспечивает задачам монопольный доступ к общим (критическим) ресурсам.

Метод критической секции имеет ряд преимуществ перед его аналогами. Так, например, использование семафоров (Semaphore) сложнее в реализации. Другой метод взаимных исключений –mutex– в целом похож на метод критической секции, но он требует больше системных ресурсов и имеет своё время тайм-аута, по истечении которого ожидающий процесс может всё-таки войти в защищён­ный блок, в то время как в критической секции подобного механизма нет.

Текст всей программы с необходимыми комментариями приведен в приложении А.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]