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

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

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

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

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

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

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

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

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

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

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

Q имя треда;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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