Чтение онлайн

ЖАНРЫ

Параллельное и распределенное программирование на С++
Шрифт:

Синопсис

#include <ptrhead.h>

int pthread_rwlock_init(pthread_rwlock_t *,const pthread_rwlockattr_t *);

int pthread_rwlock_destroy(pthread_rwlock_t *) ;

int pthread_rwlock_rdlock(pthread_rwlock_t *);

int pthread_rwlock_tryrdlock(pthread_rwlock_t *);

int pthread_rwlock_wrlock(pthread_rwlock_t *);

int pthread_rwlock_trywrlock(pthread_rwlock_t *);

int pthread_rwlock_unlock(pthread_rwlock_t *);

int pthread_rwlockattr_init(pthread_rwlockattr_t *);

int pthread_rwlockattr_destroy(pthread_rwlockattr_t *);

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *,int *);

int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *, int) ;

// Листинг 11.9. Объявление класса rw_mutex, который

// содержит объекты типа pthread_rwlock_ t

// и pthread_rwlockattr_t

class rw_mutex : public mutex{

protected:

struct pthread_rwlock_t *RwLock;

struct pthread_rwlockattr_t *RwLockAttr;

public:

//.. .

int read_lock(void);

int write_lock(void);

int try_readlock(void);

int try_writelock(void);

//.. .

};

Класс rw_mutex

наслелует класс mutex. На рис. 11.3 показаны отношения между классами rw_mutex, mutex, synchronization_variable и runtime_error.

Рис. 11.3. Отношения между классами rw_mutex, mutex, synchronization_variable и runtime_error

Пока мы создаем «узкий» интерфейс. На данном этапе мы заинтересованы в обеспечении минимального набора атрибутов и характеристик, необходимых для обобщения нашего класса mutex с использованием мьютексных типов и функций из библиотеки Pthread. Но после создания «узкого» интерфейса для класса mutex мы воспользуемся им как основой для создания «полуширокого» интерфейса. «Узкий» интерфейс обычно применяется в отношении классов, которые связаны наследованием. «Широкие» интерфейсы, как правило, применяют к классам, которые связаны функциями, а не наследованием. Нам нужен интерфейсный класс для упрощения работы с классами или функциями, которые принадлежат различным библиотекам, но выполняют подобные действия. Интерфейсный класс должен обеспечить программиста удобными рабочими инструментами. Для этого мы берем все библиотеки или классы с подобными функциями, отбираем все общие функции и переменные и после некоторого обобщения помещаем их в большой класс, который содержит все требуемые функции и атрибуты. Так определяется класс с «широким» интерфейсом. Но если включить в него (например, в класс rw_mutex) только интересующие нас функции и данные, мы получим «полуширокий» интерфейс. Его преимущества перед «широким» интерфейсом заключаются в том, что он позволяет нам получать доступ к объектам, которые связаны лишь функционально, и ограничивает множество методов, которыми может пользоваться программист, теми, которые содержатся в интерфейсном классе с узким «силуэтом». Это может быть очень важно при интеграции таких больших библиотек функций, как MPI и PVM с POSIX-возможностями параллелизма. Объединение MPI-, PVM- и POSIX-средств дает сотни функций с аналогичными целями. Затратив время на упрощение этой функциональности в интерфейсных классах, вы позволите программисту понизить уровень сложности, связанный с параллельным и распределенным программированием. Кроме того, эти интерфейсные классы становятся компонентами, которые можно многократно использовать в различных приложениях.

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

Определение класса semaphore показано в листинге 11.10.

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

class semaphore : public synchronization_variable( protected:

sem_t * Semaphore; public://.. .

int lock(void);

int unlock(void);

int trylock(void);

//. . .

};

Синопсис

<semaphore.h>

int sem_init(sem_t *, int, unsigned int) ;

int sem_destroy(sem_t *);

sem_t *sem_open(const char *, int, ...);

int sem_close(sem_t *);

int sem_unlink(const char *);

int sem_wait(sem_t *);

int sem_trywait(sem_t *);

int sem_post(sem_t *);

int sem_getvalue(sem__t *, int *);

Обратите внимание на то, что класс semaphore имеет такой же интерфейс, как и наш класс mutex. Чем же они различаются? Хотя интерфейсы классов mutex и semaphore одинаковы, реализация функций lock , unlock , trylock и тому подобных представляет собой вызовы семафорных функций библиотеки POSIX .

// Листинг 11.11. Определение методов lock, unlock

и

// trylock для класса semaphore

int semaphore::lock(void) (

//.. .

return(sem_wait(Semaphore));

}

int semaphore::unlock(void) {

//. . .

return(sem_post(Semaphore));

}

Итак, теперь функции lock , unlock , trylock и тому подобные заключают в оболочку семафорные функции библиотеки POSIX, а не функции библиотеки Pthread. Важно отметить, что семафор и мьютекс — не одно и то же. Но их можно использовать в аналогичных ситуациях. Зачастую с точки зрения инструкций, которые реализуют параллелизм, механизмы функций lock и unlock имеют одно и то же назначение. Некоторые основные различия между мьютексом и семафором указаны в табл. 11.2.

Таблица 11.2. Ос н овные различия между мью т ексами и семафорами

Характеристики мьютексов

• Мьютексы и переменные условий разделяются между потоками

• Мьютекс деблокируется теми же потоками, которые его заблокировали

• Мьютекс либо блокируется, либо деблокируется

Характеристики семафоров

• Семафоры обычно разделяются между процессами, но их разделение возможно и между потоками

• - Освобождать семафор должен необязательно тот процесс или поток, который его удерживал

• Семафоры управляются количеством ссылок. Стандарт POSIX включает именованные семафоры

Несмотря на важность различий в семантике (см. табл. 11.2), часто их оказывается недостаточно для оправдания применения к семафорам и мьютексам совершенно различных интерфейсов. Поэтому мы оставляем «полуширокий» интерфейс для функций lock, unlock и trylock с одним предостережением: программист должен знать различия между мьютексом и семафором. Это можно сравнить с ситуацией, которая возникает с такими «широкими» интерфейса м и таких контейнерных классов, как deque, queue, set, multiset и пр. Эти контейнерные классы связаны общим интерфейсом, но их семантика в определенных областях различна. Используя понятие интерфейсного класса, можно разработать соответствующие компоненты синхронизации для мьютексов, переменных условий, мьютексов чтения-записи и семафоров. Имея такие компоненты, мы можем спроектировать безопасные (с точки зрения параллелизма) контейнерные, доменные и каркасные классы. Мы можем также применять интерфейсные классы для обеспечения единого интерфейса с различными версиями одной и той же библиотеки функций при необходимости использования обеих версий (по разным причинам) в одном и том же приложении. Иногда интерфейсный класс может быть успешно применен для безболезненного перехода от устарелых функций к новым. Если мы хотим оградить программиста от различий, существующих между операционными системами, то наша цель — обеспечить его соответствующим АРI-интерфейсом [18], независимо от того, какая библиотека семафорных функций используется в среде: System V или POSIX.

Поддержка потокового представления

Помимо использования интерфейсных классов для упрощения программирования и создания новых «широких» интерфейсов библиотек средств параллелизма и передачи сообщений, имеет смысл также расширить существующие интерфейсы. Например, объектно-ориентированное представление потоков данных можно расширить за счет использования каналов, FIFO-очередей и таких библиотек передачи сообщений, как PVM и MPI. Эти компоненты используются ради достижения межпроцессного взаимодействия (Inter-Process Communication — IPC), межпотокового взаимодействия (Inter-Thread Communication — ITC), а в некоторых случалх и взаимодействия между объектами (Object-to-Object Communicaton — OTOC). Если взаимодействие имеет место между параллельно выполняемыми потоками или процессами, то канал связи может представлять собой критический раздел. Другими словами, если несколько процессов (потоков) попытаются одновременно обновить один и тот же канал, FIFO-очередь или буфер сообщений, непременно возникнет «гонка» данных. Если мы собираемся расширить объектно-ориентированный интерфейс потоков данных за счет включения компонентов из библиотеки PVM или MPI, нам нужно быть уверенными в том, что доступ к этим потокам данных будет безопасен с точки зрения параллелизма. Именно здесь могут пригодиться наши компоненты синхронизации, спроектированные в виде интерфейсных классов. Рассмотрим простой класс pvm_stream.

Поделиться с друзьями: