Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Подробнее об объектно-ориентированном взаимном исключении и интерфейсных классах






 

Чтобы справиться со сложностью написания и поддержки программ с параллелизмом, попробуем упростить API-интерфейс с соответствующими библиотеками. В некоторых системах, возможно, имеет смысл создать библиотеки Pthreads, MPI, атакже стандартные функции использования семафоров и разделяемой памяти как часть единого решения. Все эти библиотеки и функции имеют собственные протоколы и синтаксис. Но у них есть много общего. Поэтому мы можем использовать интерфейсные классы, наследование и полиморфизм для создания упрощенного и непротиворечивого интерфейса, с которым непосредственно будет работать программист. Мы можем также скрыть от наших приложений детали реализации конкретной библиотеки. Если приложение опирается только на методы, используемые в наших интерфейсных классах, то оно будет защищено от изменений, вносимых в реализацию функций, обновлений библиотек и прочих «подводных» реструктуризации. В конце концов, работа над интерфейсом (интерфейсными классами) с компонентами параллелизма и библиотеками функций позволит существенно понизить уровень сложности параллельного программирования. Итак, рассмотрим подробнее, какие методы разработки интерфейсных классов можно реализовать для поддержки параллелизма.

 

«Полуширокие» интерфейсы

 

Базовый POSIX-семафор используется для синхронизации доступа к критическому разделу нескольких процессов, а базовый POSIX -поток— для синхронизации доступа к критическому разделу нескольких потоков. В обоих случалх используются переменные синхронизации и ряд функций, работающих с этими переменными. Библиотеки MPI и PVM содержат примитивы передачи сообщений и обладают средствами порождения задач. Но интерфейсы этих библиотек различны. Нетрудно предположить, что работа прикладного программиста была бы эффективней, если бы он сосредоточил свое внимание на логике и структуре программы. Однако там, где семантика программы теряет свою ясность из-за необходимости использовать библиотеки, в которых попадаются аналогичные функции, а сами библиотеки отличаются синтаксисом и протоколами, у программиста возникают немалые трудности. Отсюда вытекает потребность универсализации интерфейса, который бы подходил для работы с разными библиотеками.

Существует по крайней мере два подхода к разработке общего интерфейса для семейства, или коллекции классов. Объектно-ориентированный подход начинается с общего и переходит к частностям посредством наследования. Другими словами, возьмем минимальный набор характеристик и атрибутов, которыми должен обладать каждый член рассматриваемого сехмейства классов, а затем посредством наследования будем конкретизировать характеристики для каждого класса. При таком подходе по мере «спуска» по иерархии классов интерфейс становится все более «узким». Второй подход часто используется в коллекциях шаблонов. Шаблонные методы начинаются c конкретного и переходят к более общему посредством «широких» интерфейсов. «Широкий» интерфейс включает обобщение всех характеристик и атрибутов (см. книгу Страуструпа «Язык программирования С++», 1997). Если бы нам пришлось применить к библиотекам средств параллелизма «узкий» и «широкий» интерфейсы, то согласно метолу «узкого интерфейса» мы бы взяли от каждой библиотеки общие, или пересекающиеся, части (т.е. пересечение), обобщили их и поместили в базовый класс. И, наоборот, реализуя метод «широкого интерфейса», нужно было бы поместить в базовый класс все функциональные части каждой библиотеки (т.е. объединение), предварительно обобщив их. В результате пересечения мы получили бы меньший по объему да и менее полезный класс. А результат объединения, скорей всего, поразил бы каждого своей громоздкостью. Решение, которое интересует нас в данном случае, находится где-то посередине, т.е. нам нужны «полуширокие» интерфейсы. Начнем же мы с метода «узкого» интерфейса и обобщим его настолько, насколько это можно сделать в пределах иерархии одного класса. Затем используем этот «узкий» интерфейс в качестве основы для коллекции классов, которые связаны не наследованием, а функциями. «Узкий» интерфейс должен действовать в качестве стратегии сдерживания «ширины», до которой может разбухнуть «полуширокий» интерфейс. Другими словами, нам не нужно объединять буквально все характеристики и атрибуты; мы хотим получить объединение только тех частей, которые логически связаны с нашим «узким» интерфейсом. Проиллюстрируем эту мысль иа примере простого проекта интерфейсных классов для POSIX-семафора, Pthread-мьютекса и Pthread-переменной блокировки.

Безотносительно к реализации деталей, операции блокировки, разблокировки и «пробной» блокировки являются характеристиками переменных синхронизации. Поэтому мы создадим базовый класс, который будет служить «трафаретом» для целого семейства классов. Объявление класса synchronization_variable представленовлистинге 11.7.

// Листинг 11.7. Объявление класса synchronization_variable

class synchronization_variable{

protected:

runtime_error Exception;

//...

public:

int virtual lock(void) = 0;

int virtual unlock(void) = 0;

int virtual trylock(void) = 0;

//...

};

Обратите внимание на то, что методы синхронизации класса synchronization_variable объявлены виртуальными и инициализированы значением 0. Это означает, что они являются чисто виртуальными методами, что делает класс synchronization_variable абстрактным. Из класса, который содержит одну или несколько чисто виртуальных функций, объект прямым путем создать нельзя. Чтобы использовать этот класс, необходимо вывести из него новый класс и определить в нем все чисто виртуальные функции. Абстрактный класс — это своего рода трафарет с указанием того, какие функции должны быть определены в производном классе. Он предлагает интерфейсный проект для производных классов. Он отнюдь не диктует, как нужно реализовать методы, он лишь отмечает, какие методы должны быть представлены в выведенном классе, причем они не могут оставаться в нем чисто виргуальными. С помощью имен этих методов мы можем подсказать предполагаемое их поведение. Таким образом, проектный интерфейсный класс предлагает проект без реализации. Класс этого типа используется в качестве фундамента для будущих классов. Проектный класс гарантирует, что интерфейс будет иметь определенный вид [9]. Класс synchronization_variable обеспечивает интерфейсный трафарет для ceмейства переменных синхронизации. Для обеспечения различных вариантов реализации интерфейса мы используем наследование. Pthread-мьютекс — прекрасный кандидат для интерфейсного класса, поэтому мы определяем класс mutex как производный от класса synchronization_variable.

// Листинг 11.8. Объявление класса мьютекс, который

// наследует класс synchronization_variable

class mutex: public synchronization_variable {

protected:

pthread_mutex_t *Mutex;

pthread_mutexattr_t *MutexAttr;

//...

public:

int lock(void);

int unlock(void);

int trylock(void);

//...

};

Класс mutex должен обеспечить реализации для всех чисто виртуальных функций. Если эти функции определены, значит, политика, предложеннал абстрактным классом, выдержана. Класс mutex теперь не является абстрактным, поэтому из него и из его потомков можно создавать объекты. Каждый из методов класса mutex заключает в оболочку соответствующую Pthread-функцию. Например, код

int mutex:: trylock(void) {

//...

return(pthread_mutex_trylock(Mutex); //...

}

обеспечивает интерфейс для функции pthread_mutex_trylock(). Интерфейсные варианты функций lock(), unlock() и trylock() упрощают вызовы функций библиотеки Pthread. Наша цель — использовать инкапсуляцию и наследование для определения всего семейства мьютексных классов. Процесс наследования — это процесс специализации. Производный класс включает дополнительные атрибуты или характеристики, которые отличают его от предков. Каждый атрибут или характеристика, добавленная в производный класс, конкретизирует его. Теперь мы, используя наследование, можем спроектировать специализацию класса mutex путем введения понятия мьютексного класса, способного обеспечить чтение и запись. Наш обобщенный класс mutex предназначен для защиты доступа к критическому разделу. Если один поток заблокировал мьютекс, он получает доступ к критическому разделу, защищаемому этим мьютексом. Иногда такая мера предосторожности оказывается излишне суровой. Возможны ситуации, когда вполне можно разрешить доступ нескольких потоков к одним и тем же данным, если ни один из этих потоков не модифицирует данные. Другими словами, в некоторых случаях мы можем ослабить блокировку критического раздела и «намертво» запирать его только для действий, которые стремятся модифицировать данные, разрешал при этом доступ для действий, которые предполагают лишь считывание или копирование данных. Такой вид блокировки называется блокировкой считывания (read lock). Блокировка считывания позволяет параллельный доступ к критическому разделу для чтения данных. Критический раздел может быть уже заблокированным одним потоком, но другой поток также может получить блокировку, если у него нет намерения изменять данные. Критический раздел может быть заблокирован для записи одним потоком, а другой поток может запросить блокировку для чтения этого критического раздела.

Архитектура «классной доски» служит прекрасным примером структуры, которая может использовать преимущества «мьютексов считывания» и мьютексов более общего назначения. Под «классной доской» понимается область памяти, разделяемал параллельно выполняемыми задачами. «Классная доска» используется для хранения решений некоторой проблемы, которую совместными усилиями решает целая группа задач. По мере приближения задач к решению проблемы каждая из них записывает результаты на «классную доску» и просматривает ее содержимое с целью поиска результатов, сгенерированных другими задачами, которые могут оказаться полезными для нее. Структура «классной доски» является критическим разделом. В действительности мы хотим, чтобы одновременно только одна задача могла обновлять содержимое «классной доски». Однако ее одновременное считывание мы можем позволить любому количеству задач. Кроме того, если несколько задач уже считывает содержимое «классной доски», нам нужно, чтобы оно не начало обновляться до тех пор, пока все эти задачи не завершат чтение. «Мьютекс считывания» как раз подходит для такой ситуации, поскольку он может управлять доступом к «классной доске», разрешал его только считывающим задачам и запрещал его для записывающих задач. Но если решение проблемы будет найдено, содержимое «классной доски» необходимо обновить. В процессе обновления нам нужно, чтобы ни одна считывающал задача не получила доступ к критическому разделу. Мы хотим заблокировать доступ для чтения до тех пор, пока не завершит обновление записывающал задача. Следовательно, нам нужно создать «мьютекс записи». В любой момент времени удерживать этот «мьютекс записи» может только одна задача. Поэтому мы делаем различие между мьютексом, который блокируется для считывания, но не для записи, и мьютексом, который блокируется для записи, но не для считывания. С использованием мьютекса считывания у нас может быть несколько параллельных считывающих задач, а с использованием мьютекса записи — только одна записывающал задача. Описаннал схема является частью модели CREW (Concurrent Read Exclusive Write — параллельное чтение, монопольнал запись) параллельного программирования.

Для разработки спецификации нашего мьютексного класса нам нужно наделить его способностью выполнять блокировки считывания и блокировки записи. В библиотеке Pthreads предусмотрены мьютексные переменные блокировки чтения-записи и атрибутов:

pthread_rwlock_t и pthread_rwlockattr_t

Эти переменные используются совместно с 11ю pthread_rwlock()-функциями. Мы используем наш интерфейсный класс rw_mutex для инкапсуляции переменных pthread_rwlock_t и pthread_rwlockattr_t, а также для заключения в оболочку Pthread-функций мьютексной организации чтения-записи.






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.