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

syncro

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

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

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

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

5.3.1. Операции по управлению семафором

Как упоминалось выше, к семафору можно получить доступ только с помощью спе циальных операций, подобных тем, которые выполняются с объектами. Это операции декремента, P(), и инкремента, V(). Если объект Mutex представляет собой семафор, то логика реализации операций P(Mutex) и V(Mutex) может выглядеть таким образом:

P(Mutex) if(Mutex > 0){

Mutex--;

}

else {

Блокирование по объекту Mutex;

}

V(Mutex)

if(Очередь доступа к объекту Mutex не пуста){ Передача объекта Мьютекс следующей задаче;

}

else{

Mutex++;

}

Реализация зависит от конкретной системы. Эти операции неделимы, т.е. их не возможно прервать. Если операцию P() попытаются выполнить сразу несколько за дач, то лишь одна из них получит разрешение продолжить работу. Если объект Mutex был уже декрементирован, то задача будет заблокирована и займет место в очереди. Операция V() вызывается задачей, которая имеет доступ к объекту Mutex. Если полу чения доступа к объекту Мьютекс ожидают другие задачи, он “передается” следующей задаче из очереди. Если очередь задач пуста, объект Mutex инкрементируется.

Операции с семафором могут иметь другие имена:

Операция P():

Операция V():

lock()

unlock()

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

Значение семафора зависит от его типа. Двоичный семафор будет иметь значение 0 или 1. Вычислительный семафор (определяющий лимиты ресурсов для процессов, получающих доступ к ним) может иметь некоторое неотрицательное целочисленное значение.

Стандарт POSIX определяет несколько типов семафоров. Эти семафоры исполь зуются процессами или потоками. Типы семафоров (а также их некоторые основные операции) перечислены в табл. 5.1.

Таблица 5.1. Типы семафоров, определенные стандартом POSIX

Тип семафора

Пользователь

Описание

Мьютексный семафор

Процессы или

Механизм, используемый для реализации вза

 

потоки

имного исключения в критическом разделе кода

Блокировка для обеспе

Процессы или

Механизм, используемый для реализации страте

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

потоки

гии доступа для чтения и записи среди потоков

Условная переменная

Процессы или

Механизм, используемый для уведомления по

 

потоки

токов о том, что произошло событие.

 

 

Событийный мьютекс остается заблокирован

 

 

ным потоком до тех пор, пока не будет получен

 

 

соответствующий сигнал

Несколько условных

Процессы или

Аналогичен событийному мьютексу, но включа

переменных

потоки

ет несколько событий или условий

 

 

 

Операционные системы, которые не противоречат спецификации Single UNIX Specification или стандарту POSIX Standard, поддерживают реализацию семафоров, которые являются частью библиотеки libpthread (соответствующие функции объ явлены в заголовке pthread.h).

5.3.2. Мьютексные семафоры

Стандарт POSIX определяет мьютексный семафор, используемый потоками и про цессами, как объект типа pthread_mutex_t. Этот мьютекс обеспечивает базовые опе рации, необходимые для функционирования практического механизма синхронизации:

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

запрос на монопольное использование;

отказ от монопольного использования;

тестирование монопольного использования;

разрушение.

Функции класса pthread_mutex_t, которые используются для выполнения этих базовых операций, перечислены в табл. 5.2. Во время инициализации выделяется па мять, необходимая для функционирования мьютексного семафора, и объекту сема фора присваивается некоторое начальное значение. Для двоичного семафора на чальным может быть значение 0 или 1. Начальным значением вычислительного се мафора может быть неотрицательное число, которое представляет количество

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

доступных ресурсных единиц. Семафорное значение можно использовать для представ ления предельного количества запросов, которое способна обработать программа в одном сеансе. В отличие от обычных переменных, в инициализации которых сомневаться не приходится, факт инициализации мьютекса с помощью вызова соответствующей функции гарантировать невозможно. Чтобы убедиться в том, что мьютекс проинициализирован, необходимо после вызова операции инициализации принять некоторые меры предос торожности (например, проверить значение, возвращаемое соответствующей мьютекс ной функцией, или значение переменной errno). Системе не удастся создать мьютекс, если окажется, что занята память, предусмотренная ранее для мьютексов, или превышено допустимое количество семафоров, или семафор с данным именем уже существует, или же имеет место какая то другая проблема, связанная с выделением памяти.

Таблица 5.2. Функции класса pthread_mutex_t

Мьютексные операции

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

 

#include <pthread.h>

Инициализация

int pthread_mutex_init(

 

pthread_mutex_t *restrict mutex,

 

const pthread_mutexattr_t *restrict attr);

 

pthread_mutex_t mutex =

 

PTHREAD_MUTEX_INITIALIZER;

Запрос на монопольное

<time.h>

использование

int pthread_mutex_lock(

 

pthread_mutex_t *mutex);

 

int pthread_mutex_timedlock(

 

pthread_mutex_t *restrict mutex,

 

const struct tiemspec *restrict abs_timeout);

Отказ от монопольного

int pthread_mutex_unlock(

использования

pthread_mutex_t *mutex);

Тестирование монопольного

int pthread_mutex_trylock(

использования

pthread_mutex_t *mutex);

Разрушение

int pthread_mutex_destroy(

 

pthread_mutex_t *mutex);

 

 

Подобно потокам, мьютекс библиотеки Pthread имеет атрибутный объект (он рассматривается ниже), который инкапсулирует все атрибуты мьютекса. Этот атрибутный объект можно передать функции инициализации, в результате чего бу дет создан мьютекс с атрибутами, заданными с помощью этого объекта. Если при инициализации атрибутный объект не используется, мьютекс будет инициализирован значениями, действующими по умолчанию. Объект типа pthread_mutex_t инициа лизируется как деблокированный и закрытый. Закрытый мьютекс разделяется между потоками одного процесса. Разделяемый мьютекс совместно используется потоками нескольких процессов. При использовании атрибутов, действующих по умолчанию, мьютекс может быть инициализирован статически для статических мьютексных объектов с помощью следующего макроса:

pthread_mutext Mutex = PTHREAD_MUTEX_INITIALIZER;

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

Этот метод менее затратный, но в нем не предусмотрено проверки ошибок. Мьютекс может иметь или не иметь владельца. Операция запроса на монопольное ис

пользование предоставляет право владения мьютексом вызывающему потоку или про цессу. После того как мьютекс обрел владельца, поток (или процесс) получает моно польный доступ к запрашиваемому ресурсу. При попытке завладеть “уже занятым” мьютексом (путем вызова этой операции), совершенной любыми другими потоками или процессами, они будут заблокированы до тех пор, пока мьютекс не станет доступ ным. При освобождении мьютекса следующий (по очереди) поток или процесс (который был заблокирован) деблокируется и получает право собственности на этот мьютекс. И освободить его может только поток, получивший данный мьютекс во вла дение с помощью функции pthread_mutex_lock(). Можно также использовать синхронизированную версию этой функции. В этом случае, если мьютекс несвободен, то процесс или поток будет ожидать в течение заданного промежутка времени. Если мьютекс за это время не освободится, то процесс или поток продолжит выполнение.

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

При выполнении операции разрушения освобождается память, связанная с мьютек сом. Память не освобождается, если у мьютекса есть владелец или если поток (или процесс) ожидают права на владение им.

5.3.2.1. Использование мьютексного атрибутного объекта

Мьютексный объект типа pthread_mutex_t можно использовать вместе с атрибут ным объектом подобно атрибутному объекту потока. Мьютексный атрибутный объект инкапсулирует все атрибуты объекта мьютекса. После инициализации его могут исполь зовать несколько мьютексных объектов, передавая в качестве параметра функции pthread_mutex_init(). Мьютексный атрибутный объект определяет ряд функций, используемых для установки таких атрибутов, как предельный приоритет, протокол и тип. Эти и другие функции мьютексного атрибутного объекта перечислены в табл. 5.3.

Таблица 5.3. Функции доступа к мьютексному атрибутному объекту

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

Описание

#include <pthread.h>

int pthread_mutexattr_init (pthread_mutexattr_t * attr);

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

int pthread_mutexattr_destroy (pthread_mutexattr_t * attr);

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

 

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

 

Продолжение табл. 5.3

 

 

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

Описание

int pthread_mutexattr_setprioceiling

(pthread_mutexattr_t * attr, int prioceiling);

int pthread_mutexattr_getprioceiling

(const pthread_mutexattr_t * restrict attr, int *restrict prioceiling);

Устанавливает и возвращает атрибут предель ного приоритета мьютекса, заданного парамет ром attr. Параметр prioceiling содержит значение предельного приоритета мьютекса. Атрибут prioceiling определяет минималь ный уровень приоритета, при котором еще вы полняется критический раздел, защищаемый мьютексом. Значения, которые попадают в этот диапазон приоритетов, определяются страте гией планирования SCHED_FIFO

int pthread_mutexattr_setprotocol

(pthread_mutexattr_t * attr, protocol int protocol);

int pthread_mutexattr_getprotocol

(const pthread_mutexattr_t * restrict attr,

int *restrict protocol);

int pthread_mutexattr_setpshared

(pthread_mutexattr_t * attr, int pshared);

int pthread_mutexattr_getpshared

(const pthread_mutexattr_t * restrict attr,

int *restrict pshared);

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

PTHREAD_PRIO_NONE

(на приоритет и стратегию планирования пото ка владение мьютексом не оказывает влияния);

PTHREAD_PRIO_INHERIT

(при таком протоколе поток, блокирующий другие потоки с более высокими приоритета ми, благодаря владению таким мьютексом бу дет выполняться с самым высоким приорите том из приоритетов потоков, ожидающих ос вобождения любого из мьютексов, которыми владеет данный поток);

PTHREAD_PRIO_PROTECT

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

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

PTHREAD_PROCESS_SHARED

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

PTHREAD_PROCESS_PRIVATE

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

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

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

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

Описание

int pthread_mutexattr_settype

(pthread_mutexattr_t * attr, int type);

int pthread_mutexattr_gettype

(const pthread_mutexattr_t * restrict attr,

int *restrict type);

Устанавливает и возвращает атрибут мьютекса type мьютексного атрибутного объекта, за данного параметром attr. Атрибут мьютекса type позволяет определить, будет ли мьютекс распознавать взаимоблокировку, проверять ошибки и т.д. Параметр type может содер жать такие значения:

PTHREAD_MUTEX_DEFAULT

PTHREAD_MUTEX_RECURSIVE

PTHREAD_MUTEX_ERRORCHECK

PTHREAD_MUTEX_NORMAL

Самый большой интерес представляет установка атрибута, связанного с тем, каким должен быть мьютекс: закрытым или разделяемым. Закрытые мьютексы разделяются между потоками одного процесса. Можно либо объявить мьютекс глобальным, либо организовать передачу дескриптора между потоками. Разделяемые мьютексы исполь зуются потоками, имеющими доступ к памяти, в которой размещен данный мьютекс. Такой мьютекс могут разделять потоки различных процессов. Принцип действия за крытого и разделяемого мьютексов показан на рис. 5.5. Если разделять мьютекс при ходится потокам различных процессов, его необходимо разместить в памяти, которая является общей для этих процессов. В библиотеке POSIX определено несколько функций, предназначенных для распределения памяти между объектами с помощью отображаемых в памяти файлов и объектов разделяемой памяти. В процессах мью тексы можно использовать для защиты критических разделов, которые получают дос туп к файлам, каналам, общей памяти и внешним устройствам.

5.3.2.2. Использование мьютексных семафоров для управления критическими разделами

Мьютексы используются для управления критическими разделами процессов и по токов, чтобы предотвратить возникновение условий “гонок”. Мьютексы позволяют избежать условий “гонок”, реализуя последовательный доступ к критическому разде лу. Рассмотрим код листинга 5.1. В нем демонстрируется выполнение двух потоков. Для защиты их критических разделов и используются мьютексы.

// Листинг 5.1. Использование мьютексов для защиты

//критических разделов потоков

//...

pthread_t ThreadA,ThreadB; pthread_mutex_t Mutex; pthread_mutexattr_t MutexAttr;

void *task1(void *X)

{

pthread_mutex_lock(&Mutex);

// Критический раздел кода. pthread_mutex_unlock(&Mutex);

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

return(0);

}

void *task2(void *X)

{

pthread_mutex_lock(&Mutex);

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

}

int main(void)

{

//...

pthread_mutexattr_init(&MutexAttr);

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

pthread_create(&ThreadA,NULL,task1,NULL); pthread_create(&ThreadB,NULL,task2,NULL); //...

return(0);

}

Влистинге 5.1 потоки ThreadA и ThreadB содержат критические разделы, защи щаемые с помощью объекта Mutex.

Влистинге 5.2 демонстрируется, как можно использовать мьютексы для защиты критических разделов процессов.

//Листинг 5.2. Использование мьютексов для защиты

//критических разделов процессов

//...

int Rt; pthread_mutex_t Mutex1;

pthread_mutexattr_t MutexAttr;

int main(void)

{

//...

pthread_mutexattr_init(&MutexAttr); pthread_mutexattr_setpshared(&MutexAttr,

PTHREAD_PROCESS_SHARED);

pthread_mutex_init(&Mutex1,&MutexAttr); if((Rt = fork()) == 0){ // Сыновний процесс.

pthread_mutex_lock(&Mutex1);

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

}

else{ // Родительский процесс.

pthread_mutex_lock(&Mutex1);

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

}

//...

return(0);

}

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

АДРЕСНОЕ ПРОСТРАНСТВО ПРОЦЕССА A

Поток A

Запись

 

 

Закрытый

 

мьютекс

Поток C

Запись

 

АДРЕСНОЕ ПРОСТРАНСТВО ПРОЦЕССА B

Поток A

Запись

 

 

Разделяемый

 

мьютекс

Поток C

Запись

 

АДРЕСНОЕ ПРОСТРАНСТВО

ПРОЦЕССА A

 

АДРЕСНОЕ ПРОСТРАНСТВО ПРОЦЕССА B

 

 

 

Запись

 

 

Чтение

 

 

 

 

Поток A

 

 

 

 

Поток A

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Разделяемый

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Запись

мьютекс

Чтение

 

 

 

 

Поток C

 

 

 

 

Поток C

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 5.5. Закрытые и разделяемые мьютексы

Важно отметить, что в листинге 5.2 при вызове следующей функции мьютекс ини циализируется как разделяемый:

pthread_mutexattr_setpshared(&MutexAttr, PTHREAD_PROCESS_SHARED);

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

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

5.3.3. Блокировки для чтения и записи

Мьютексные семафоры позволяют управлять критическими разделами, обеспе чивая последовательный вход в эти разделы. В любой момент времени вход в кри тический раздел разрешается только одному потоку или процессу. Реализуя блоки ровки для чтения и записи, можно разрешить вход в критический раздел сразу не скольким потокам, если они намерены лишь считывать данные из разделяемой памяти. Следовательно, блокировкой для чтения может владеть любое количество потоков. Но если сразу несколько потоков должны записывать или модифициро вать данные общей памяти, то доступ для этого будет предоставлен только одному потоку. Другими словами, никаким другим потокам не будет разрешено входить в критический раздел, если одному потоку предоставлен монопольный доступ для записи в разделяемую память. Такой подход может оказаться полезным, если при ложения чаще считывают данные, чем записывают их. Если в приложении создает ся множество потоков, организация взаимно исключающего доступа может ока заться излишней предосторожностью. Производительность такого приложения может значительно увеличиться, если в нем разрешить одновременное считывание данных несколькими потоками. Стандарт POSIX определяет механизм блокировки для чтения и записи посредством типа pthread_rwlock_t.

Блокировки для чтения и записи имеют такие же операции, как и мьютексные се мафоры. Они перечислены в табл. 5.4.

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

pthread_rwlock_rdlock() pthread_rwlock_wrlock()

Функция pthread_rwlock_rdlock() предоставляет вызывающему потоку блоки ровку чтения, а функция pthread_rwlock_wrlock() — блокировку записи. Запросив блокировку чтения, поток получит ее в том случае, если нет потоков, удерживающих блокировку записи. Если же таковые имеются, вызывающий поток блокируется. Если поток запросит блокировку записи, он ее получит в том случае, если нет потоков, удерживающих блокировку чтения или блокировку записи. Если же таковые имеются, вызывающий поток блокируется.

Блокировка чтения записи реализуется с помощью объектов типа pthread_rwlock_t. Этот же тип имеет атрибутный объект, который инкапсули рует атрибуты объекта блокировки. Функции установки и чтения атрибутов пере числены в табл. 5.5.

Объект типа pthread_rwlock_t может быть закрытым (для разделения между потоками одного процесса) или разделяемым (для разделения между потоками раз личных процессов).

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

Таблица 5.4. Операции, используемые для блокировки чтения-записи

Операции

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

 

#include <pthread.h>

Инициализация

int pthread_rwlock_init(

 

pthread_rwlock_t *restrict rwlock,

 

const pthread_rwlockattr_t *restrict attr);

Запрос на блокировку

#include <time.h>

 

int pthread_rwlock_rdlock(

 

pthread_rwlock_t *rwlock);

 

int pthread_rwlock_wrlock(

 

pthread_rwlock_t *rwlock);

 

int pthread_rwlock_timedrdlock(

 

pthread_rwlock_t *restrict rwlock,

 

const struct timespec *restrict

 

abs_timeout);

 

int pthread_rwlock_timedwrlock(

 

pthread_rwlock_t | *restrict rwlock,

 

const struct timespec *restrict

 

abs_timeout);

Освобождение

int pthread_rwlock_unlock(

блокировки

pthread_rwlock_t *rwlock);

Тестирование

int pthread_rwlock_tryrdlock(

блокировки

pthread_rwlock_t *rwlock);

 

int pthread_rwlock_trywrlock(

 

pthread_rwlock_t *rwlock);

Разрушение

int pthread_rwlock_destroy(

 

pthread_rwlock_t *rwlock);

 

 

Таблица 5.5. Функции доступа к атрибутному объекту типа pthread_rwlock_t

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

Описание

#include <pthread.h>

int pthread_rwlockattr_init (pthread_rwlockattr_t * attr);

int pthread_rwlockattr_destroy (pthread_rwlockattr_t * attr);

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

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

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