Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

syncro

.pdf
Скачиваний:
7
Добавлен:
21.05.2015
Размер:
328.14 Кб
Скачать

 

5.3. Что такое семафоры 203

 

Окончание табл. 5.5

 

 

Прототипы функций

Описание

int pthread_rwlockattr_setpshared

(pthread_rwlockattr_t * attr, int pshared);

Устанавливает или возвращает атрибут process-shared атрибутного объекта бло кировки чтения записи, заданного парамет ром attr. Параметр pshared может содер

int pthread_rwlockattr_getpshared

(const pthread_rwlockattr_t * restrict attr,

int *restrict pshared);

жать следующие значения:

PTHREAD_PROCESS_SHARED

(разрешает блокировку чтения записи, разде ляемую любыми потоками, которые имеют дос туп к памяти, выделенной для этого объекта блокировки, даже если потоки принадлежат различным процессам);

PTHREAD_PROCESS_PRIVATE

(блокировка чтения записи разделяется меж ду потоками одного процесса)

5.3.3.1. Использование блокировок чтения-записи для реализации стратегии доступа

Блокировки чтения записи можно использовать для реализации стратегии доступа CREW (параллельное чтение и исключающая запись). Согласно этой стратегии воз можность параллельно считывать данные может быть предоставлена сразу несколь ким задачам, но только одна задача получит право доступа для записи. При выполне нии монопольной записи в этом случае не будет дано разрешение на параллельное чтение данных. Использование блокировок чтения записи для защиты критических разделов продемонстрировано в листинге 5.3.

// Листинг 5.3. Пример использования потоками блокировок

//чтения-записи

//...

pthread_t ThreadA,ThreadB,ThreadC,ThreadD; pthread_rwlock_t RWLock;

void *producer1(void *X)

{

pthread_rwlock_wrlock(&RWLock);

// Критический раздел. pthread_rwlock_unlock(&RWLock); return(0);

}

void *producer2(void *X)

{

pthread_rwlock_wrlock(&RWLock);

// Критический раздел. pthread_rwlock_unlock(&RWLock);

}

204 Глава 5. Синхронизация параллельно выполняемых задач

void *consumer1(void *X)

{

pthread_rwlock_rdlock(&RWLock);

// Критический раздел. pthread_rwlock_unlock(&RWLock); return(0);

}

void *consumer2(void *X)

{

pthread_rwlock_rdlock(&RWLock);

// Критический раздел. pthread_rwlock_unlock(&RWLock); return(0);

}

int main(void)

{

pthread_rwlock_init(&RWLock,NULL); // Устанавливаем атрибуты мьютекса.

pthread_create(&ThreadA,NULL,producer1,NULL); pthread_create(&ThreadB,NULL,consumer1,NULL); pthread_create(&ThreadC,NULL,producer2,NULL); pthread_create(&ThreadD,NULL,consumer2,NULL); //...

return(0);

}

В листинге 5.3 создаются четыре потока. Два потока, ThreadA и ThreadC, выпол няют роль изготовителей, а остальные два (ThreadB и ThreadD) — потребителей. Все потоки имеют критический раздел, который защищается объектом блокировки чте ния записи RWLock. Потоки ThreadB и ThreadD могут входить в свои критические разделы параллельно или последовательно, но это исключено, если поток ThreadA или ThreadC пребывает в своем критическом разделе. Потоки ThreadA и ThreadC не могут входить в свои критические разделы параллельно. Частичная таблица решений для листинга 5.3 показана в табл. 5.6.

Таблица 5.6. Частичная таблица решений для листинга 5.3

Поток A

Поток B

Поток C

Поток D

(выполняет запись)

(выполняет чтение)

(выполняет запись)

(выполняет чтение)

 

 

 

 

Нет

Нет

Нет

Да

Нет

Нет

Да

Нет

Нет

Да

Нет

Нет

Нет

Да

Нет

Да

Да

Нет

Нет

Нет

 

 

 

 

5.3. Что такое семафоры 205

5.3.4. Условные переменные

Условная переменная представляет собой семафор, используемый для сигнализации о событии, которое произошло. Сигнала о том, что произошло некоторое событие, может ожидать один или несколько процессов (или потоков) от других процессов или потоков. Следует понимать различие между условными переменными и рассмотрен ными выше мьютексными семафорами. Назначение мьютексного семафора и блоки ровок чтения записи — синхронизировать доступ к данным, в то время как условные переменные обычно используются для синхронизации последовательности опера ций. По этому поводу в своей книге UNIX Network Programming прекрасно высказался Ричард Стивенс (W. Richard Stevens): “Мьютексы нужно использовать для блокирова ния, а не для ожидания”.

В листинге 4.6 поток “потребитель” содержал цикл:

15while(TextFiles.empty())

16{}

Поток “потребитель” выполнял итерации цикла до тех пор, пока в очереди TextFiles были элементы. Этот цикл можно заменить условной переменной. Поток “изготовитель” сигналом уведомляет потребителя о том, что в очередь помещены элементы. Поток “потребитель” может ожидать до тех пор, пока не получит сигнал, а затем перейдет к обработке очереди.

Условная переменная имеет тип pthread_cond_t. Ниже перечислены типы опе раций, которые может она выполнять:

инициализация;

разрушение;

ожидание;

ожидание с ограничением по времени;

адресная сигнализация;

всеобщая сигнализация;

Операции инициализации и разрушения выполняются условными переменными подобно аналогичным операциям других мьютексов. Функции класса pthread_cond_t, которые реализуют эти операции, перечислены в табл. 5.7.

Таблица 5.7. Функции класса pthread_cond_t, которые реализуют операции условных переменных

Операции

Прототипы функций (макросы)

#include <pthread.h>

Инициализация int pthread_cond_init( pthread_cond_t *restrict cond,

const pthread_condattr_t *restrict attr);

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

206 Глава 5. Синхронизация параллельно выполняемых задач

Окончание табл. 5.7

Операции

Прототипы функций (макросы)

Ожидание

int pthread_cond_wait(

 

pthread_cond_t * restrict cond,

 

pthread_mutex_t *restrict mutex);

 

int pthread_cond_timedwait(

 

pthread_cond_t * restrict cond,

 

pthread_mutex_t *restrict mutex,

 

const struct timespec *restrict abstime);

Сигнализация

int pthread_cond_signal(

 

pthread_cond_t *cond);

 

int pthread_cond_broadcast(

 

pthread_cond_t *cond);

Разрушение

int pthread_cond_destroy(

 

pthread_cond_t *cond);

 

 

Условные переменные используются совместно с мьютексами. При попытке за блокировать мьютекс поток или процесс будет заблокирован до тех пор, пока мью текс не освободится. После разблокирования поток или процесс получит мьютекс и продолжит свою работу. При использовании условной переменной ее необходи мо связать с мьютексом.

//...

pthread_mutex_lock(&Mutex);

pthread_cond_wait(&EventMutex,&Mutex);

//...

pthread_mutex_unlock(&Mutex);

Итак, некоторая задача делает попытку заблокировать мьютекс. Если мьютекс уже заблокирован, то эта задача блокируется. После разблокирования задача освободит мьютекс Мьютекс и при этом будет ожидать сигнала для условной переменной EventMutex. Если мьютекс не заблокирован, задача будет ожидать сигнала неограни ченно долго. При ожидании с ограничением по времени задача будет ожидать сигнала в течение заданного интервала времени. Если это время истечет до получения зада чей сигнала, функция возвратит код ошибки. Затем задача вновь затребует мьютекс.

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

Условная переменная также имеет атрибутный объект, функции которого пере числены в табл. 5.8.

5.3. Что такое семафоры 207

Таблица 5.8. Функции доступа к атрибутному объекту для условной переменной типа pthread_cond_t

Прототипы функций

Описание

#include <pthread.h>

int pthread_condattr_init( pthread_condattr_t * attr);

int pthread_condattr_destroy( pthread_condattr_t * attr);

int

pthread_condattr_setpshared( pthread_condattr_t *

attr,

int pshared);

int pthread_condattr_getpshared(

const pthread_condattr_t * restrict attr,

int *restrict pshared);

int pthread_condattr_setclock( pthread_condattr_t * attr, clockid_t clock_id);

int pthread_condattr_getclock( const pthread_condattr_t * restrict attr,

clockid_t * restrict

clock_id);

Инициализирует атрибутный объект условной переменной, заданный параметром attr, зна чениями, действующими по умолчанию для всех атрибутов, определенных реализацией

Разрушает атрибутный объект условной пере менной, заданный параметром attr. Этот объ ект можно инициализировать повторно, вы звав функцию pthread_condattr_init()

Устанавливает или возвращает атрибут process-shared атрибутного объекта условной переменной, заданного параметром attr. Па раметр pshared может содержать следующие значения:

PTHREAD_PROCESS_SHARED

(разрешает блокировку чтения записи, разде ляемую любыми потоками, которые имеют дос туп к памяти, выделенной для этой условной пе ременной, даже если потоки принадлежат раз личным процессам);

PTHREAD_PROCESS_PRIVATE

(условная переменная разделяется между пото ками одного процесса)

Устанавливает или возвращает атрибут clock атрибутного объекта условной переменной, за данного параметром attr. Атрибут clock представляет собой идентификатор часов, ис пользуемых для измерения лимита времени

в функции pthread_cond_timedwait().

По умолчанию для атрибута clock использует ся идентификатор системных часов

5.3.4.1. Использование условных переменных для управления отношениями синхронизации

Условную переменную можно использовать для реализации отношений синхрони зации, о которых упоминалось выше: старт старт (СС), финиш старт (ФС), старт финиш (СФ) и финиш финиш (ФФ). Эти отношения могут существовать между пото ками одного или различных процессов. В листингах 5.4 и 5.5 представлены примеры реализации ФС и ФФ отношений синхронизации. В каждом примере определено два мьютекса. Один мьютекс используется для синхронизации доступа к общим данным, а другой — для синхронизации выполнения кода.

208 Глава 5. Синхронизация параллельно выполняемых задач

// Листинг 5.4. ФС-отношения синхронизации между

//двумя потоками

//...

float Number;

pthread_t ThreadA,ThreadB; pthread_mutex_t Mutex,EventMutex; pthread_cond_t Event;

void *worker1(void *X)

{

for(int Count = 1;Count < 100;Count++){ pthread_mutex_lock(&Mutex); Number++;

pthread_mutex_unlock(&Mutex);

cout << "worker1: число равно " << Number << endl; if(Number == 50){

pthread_cond_signal(&Event);

}

}

cout << "Выполнение функции worker1 завершено." << endl; return(0);

}

void *worker2(void *X)

{

pthread_mutex_lock(&EventMutex); pthread_cond_wait(&Event,&EventMutex); pthread_mutex_unlock(&EventMutex); for(int Count = 1;Count < 50;Count++){

pthread_mutex_lock(&Mutex); Number = Number + 20;

pthread_mutex_unlock(&Mutex);

cout << "worker2: число равно " << Number << endl;

}

cout << "Выполнение функции worker2 завершено." << endl; return(0);

}

int main(int argc, char *argv[])

{

pthread_mutex_init(&Mutex,NULL); pthread_mutex_init(&EventMutex,NULL); pthread_cond_init(&Event,NULL); pthread_create(&ThreadA,NULL,worker1,NULL); pthread_create(&ThreadB,NULL,worker2,NULL); //...

return (0);

}

В листинге 5.4 показан пример реализации ФС отношений синхронизации. Поток ThreadA не может завершиться до тех пор, пока не стартует поток ThreadB. Если значение переменной Number станет равным 50, поток ThreadA сигнализирует об этом потоку ThreadB. Теперь он может продолжать выполнение до самого конца. Поток ThreadB не может начать выполнение до тех пор, пока не получит сигнал от потока ThreadA. Поток ThreadB использует объект EventMutex вместе с условной переменной Event. Объект Mutex используется для синхронизации доступа для запи си значения разделяемой переменной Number. Для синхронизации различных событий и доступа к критическим разделам задача может использовать несколько мьютексов.

5.3. Что такое семафоры 209

Пример реализации ФФ отношений синхронизации показан в листинге 5.5.

// Листинг 5.5. ФФ-отношения синхронизации между

//двумя потоками

//...

float Number;

pthread_t ThreadA,ThreadB; pthread_mutex_t Mutex,EventMutex; pthread_cond_t Event;

void *worker1(void *X)

{

for(int Count = 1;Count < 10;Count++){ pthread_mutex_lock(&Mutex); Number++;

pthread_mutex_unlock(&Mutex);

cout << "worker1: число равно " << Number << endl;

}

pthread_mutex_lock(&EventMutex);

cout << "Функция worker1 в состоянии ожидания. " << endl;

pthread_cond_wait(&Event,&EventMutex); pthread_mutex_unlock(&EventMutex); return(0);

}

void *worker2(void *X)

{

for(int Count = 1;Count < 100;Count++){ pthread_mutex_lock(&Mutex);

Number = Number * 2;

pthread_mutex_unlock(&Mutex);

cout << "worker2: число равно " << Number << endl;

}

pthread_cond_signal(&Event);

cout << "Функция worker2 послала сигнал " << endl; return(0);

}

int main(int argc, char *argv[])

{

pthread_mutex_init(&Mutex,NULL); pthread_mutex_init(&EventMutex,NULL); pthread_cond_init(&Event,NULL); pthread_create(&ThreadA,NULL,worker1,NULL); pthread_create(&ThreadB,NULL,worker2,NULL); //...

return (0);

}

В листинге 5.5 поток ThreadA не может завершиться до тех пор, пока не завер шится поток ThreadB. Поток ThreadA должен выполнить цикл 10 раз, а ThreadB — 100. Поток ThreadA завершит выполнение своих итераций раньше ThreadB, но будет ожидать до тех пор, пока поток ThreadB не просигналит о своем завершении.

СС и СФ отношения синхронизации невозможно реализовать подобным образом. Эти методы используются для синхронизации порядка выполнения процессов.

210 Глава 5. Синхронизация параллельно выполняемых задач

5.4. Объектно-ориентированный подход к синхронизации

Одно из преимуществ объектно ориентированного программирования состоит в защите, которую обеспечивает инкапсуляция компонентов данных объекта. Инкап суляция может обеспечить для пользователя объектов “стратегии доступа к объектам и принципы их применения” [24]. В примерах, представленных в этой главе, за при меняемые стратегии доступа вся ответственность возлагалась на пользователя дан ных. С помощью объектов и инкапсуляции ответственность можно переложить с пользователя данных на сами данные. При таком подходе создаются данные, кото рые, в отличие от функций, являются безопасными для потоков.

Для реализации такого подхода данные многопоточного приложения (по возмож ности) необходимо инкапсулировать с помощью C++ конструкций class или struct. Затем инкапсулируйте такие механизмы синхронизации, как семафоры, блокировки для обеспечения чтения записи и мьютексы событий. Если данные или механизмы синхронизации представляют собой объекты, создайте для них интерфейсный класс. Наконец, объедините объект данных с объектами синхронизации посредством насле дования или композиции, чтобы создать объекты данных, которые будут безопасны для потоков. Этот подход подробно рассматривается в главе 11.

5.5. Резюме

Для координации порядка выполнения процессов и потоков (синхронизация задач), а также доступа к разделяемым данным (синхронизация данных) можно использовать различные механизмы синхронизации. Существует четыре основных вида отношений синхронизации задач. Отношение вида “старт старт” (СС) означает, что задача A не может начаться до тех пор, пока не начнется задача B. Отношение вида “финиш старт” (ФС) означает, что задача A не может завершиться до тех пор, пока не начнет ся задача B. Отношение вида “старт финиш” (СФ) означает, что задача A не может на чаться до тех пор, пока не завершится задача B. Отношение вида “финиш финиш” (ФФ) означает, что задача A не может завершиться до тех пор, пока не завершится за дача B. Для реализации этих отношений синхронизации задач можно использовать условную переменную типа pthread_cond_t, которая определена стандартом POSIX.

Для описания синхронизации данных используются некоторые типы алгоритмов модели PRAM. Стратегию доступа EREW (исключающее чтение и исключающая за пись) можно реализовать с помощью мьютексного семафора. Мьютексный семафор защищает критический раздел, обеспечивая последовательный вход в него. Эта стра тегия разрешает либо доступ для чтения, либо доступ для записи. Стандарт POSIX оп ределяет мьютексный семафор типа pthread_mutex_t, который можно использо вать для реализации стратегии доступа EREW. Чтобы реализовать стратегию доступа CREW (параллельное чтение и исключающая запись), можно использовать блокиров ки чтения записи. Стратегия доступа CREW описывает возможность удовлетворения множества запросов на чтение, но при монопольной записи данных. Стандарт POSIX определяет объект блокировки для обеспечения чтения записи типа pthread_rwlock_t, а объектно ориентированный подход к синхронизации данных позволяет встроить механизм синхронизации в объект данных.

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