Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Семафоры взаимоисключения




В современных ОС идея семафора подверглась дальнейшей детализации, в результате было получено несколько видов семафоров, заметно отличающихся своим поведением друг от друга. Наиболее простой и естественной формой стали семафоры взаимоисключения (mutual exclusion semaphore), сокращенно именуемые (по сокращению из английского названия – mutex) семафорами. Семафоры mutex отличаются от двоичных семафоров Дейкстры тем, что операцию V над семафором может осуществить только тот процесс, который выполнил предыдущую операцию P над ним без блокировки от такого действия. Для описания и использования этого ограничения вводится понятие владения семафором. (Возможно, что происхождение семафоров mutex в качестве действительной причины – скорее психологической, чем технической – имеет понятия собственности и владения как базовых понятий западной цивилизации.)

Операции, аналогичные абстрактным P и V, над семафорами взаимоисключения называются по-разному. Так, в операционной системе OS/2 запрос на владение таким семафором (аналогичный операции P) называется DosRequestMutexSem, в Windows такой запрос использует универсальную функцию ожидания любого объекта с именем WaitForSingleObject, в операционной системе Unix запрос на владение нитью семафором mutex называется pthread_mutex_lock. Для аналога операции V в рассматриваемых ОС разработчиками приняты следующие названия: в OS/2 – DosReleaseMutexSem, а в Windows – ReleaseMutex, в Unix – pthread_mutex_unlock. Прежде чем перейти к детальному описанию упомянутых системных функций, сделаем некоторые пояснения. Запрос на владение mutex-семафором, выполняемый некоторой нитью, приостанавливает нить, выдавшую такой запрос, если этот семафор в настоящий момент принадлежит другой нити. (Конкретные действия с этими семафорами в современных ОС выполняются всегда нитью, в частности, главной нитью процесса, а процесс является собственником ресурсов.) Приостанавливается именно нить, а не весь процесс, если он состоит более чем из одной нити. Действия, выполняемые функциями DosReleaseMutexSem и ReleaseMutex в операционных системах OS/2 и Windows, называются освобождением семафора его владельцем (нитью, временно владеющей семафором). Если нить, не владевшая перед этим семафором взаимоисключения, вызывает функцию RequestMutexSem, то эта функция в качестве кода возврата дает отличное от нуля значение, обозначаемое символической константой ERROR_NOT_OWNER (с числовым значением 288). В той же ситуации функция ReleaseMutex в Windows возвращает значение ошибки, а последующий запрос кода ошибки функцией GetLastError также возвращает значение 288, символически задаваемое константой ERROR_NOT_OWNER.

Владение чем-то – дело хорошее (по крайней мере для владельца), но строгая необходимость отказа от владения только явным приказом – вызовом функции освобождения ресурса – влечет иногда серьезные проблемы применения семафоров, обеспечивающих в ОС монополизацию ресурсов. Если обратиться к описанной выше абстрактной задаче ЧИТАТЕЛИ-ПИСАТЕЛИ, то можно даже без экспериментов с отлаживаемой программой уяснить, что процесс – читатель, выполнивший вызов операции P(W), т.е. первый из процессов, начавших параллельное чтение данных, совсем не обязательно окажется и последним из группы процессов, осуществляющих одновременное чтение. Скорее даже наоборот, процесс, первым начавший чтение общих данных, через какое-то время завершит свою работу, но до ее завершения начнет чтение другой процесс и, может быть, еще какие- то процессы одновременно займутся чтением этих данных, защищаемых семафором W. Тогда последний из процессов, одновременно читавших данные (он обнаружит это по нулевому значению вспомогательного счетчика – переменной N), выполнит вызов функции, реализующей операцию V(W). Последнее должно привести в ОС OS/2 и Windows к возникновению ошибки – как результату попытки освобождения от владельца чужого владения! Вернемся теперь к описанию конкретных программных средств для использования семафоров взаимоисключения.

Семафор – не просто какая-то специальная переменная, а системный объект, особым образом используемый операционной системой. Поэтому для его создания необходимо явное приказание операционной системе. Это приказание имеет имена DosCreateMutexSem, CreateMutex и pthread_mutex_init в операционных системах OS/2, Windows и Unix соответственно. В результате этих вызовов исполняемая программа (точнее, конкретный процесс, работающий по этой программе) получают хэндл семафора взаимоисключения в качестве результата вызова. Тип этого хэндла в OS/2 и в Unix специализирован и обозначается соответственно HMTX и pthread_mutex_t. В ОС Windows для всего многообразия хэндлов предназначен единственный тип хэндла с именем HANDLE (что является потенциальным источником ошибок программистов, работающих с Windows).

Имея хэндл, нить может использовать функции запроса владения и в дальнейшем – освобождения семафора владельцем mutex. Но создание нового семафора взаимоисключения – не единственный способ получить хэндл семафора для его использования. Получить хэндл несуществующего объекта, конечно, невозможно, но можно получить хэндл уже существующего объекта. Так как глобальные данные принадлежат всем нитям процесса, то хэндл, описанный как глобальный и созданный одной нитью в процессе, становится автоматически доступным и любой другой нити того же процесса. Поэтому получение хэндла объекта, не созданного процессом, имеет смысл только для тех объектов, которые были созданы другими процессами. Для этих целей в ОС служит функция открытия объекта, созданного другим процессом, в частности, открытие mutex-семафора, созданного другим процессом. Функции открытия mutex-семафора называются в OS/2 и Windows соответственно DosOpenMutexSem и OpenMutex. В ОС Unix нет функций открытия доступа к mutex-семафору, созданному в другом процессе. Доступ к mutex-семафору, созданному в другом процессе, возможен только тогда, когда он был создан с указанием (соответствующим флагом), что семафор позволяется использовать и другими процессами.

После использования семафора в данном процессе его следует в OS/2 и Windows явно закрывать (закрывать доступ к нему) с помощью системных функций DosCloseMutexSem и CloseHandle. Функция CloseHandle применяется Windows по завершении использования в процессе всевозможных объектов и формально закрывает хэндл объекта для прекращения возможности доступа к объекту. Внутри объектов ведется внутренний счетчик процессов, использующих данный объект. При каждом выполнении функции закрытия объекта в OS/2 и Windows значение этого счетчика уменьшается на единицу. При каждом открытии объекта в OS/2 и Windows значение этого счетчика увеличивается на единицу. При создании объекта значение этого счетчика устанавливается в единицу. Если в ходе закрытия объекта значение его внутреннего счетчика уменьшится до нуля, этот объект удаляется из системы (уничтожается).

Поскольку в Unix нет функций открытия доступа к mutex-семафору, созданному в другом процессе, то нет и функции закрытия такого mutex-семафора. Для удаления же из ОС mutex-семафора (после прекращения надобности в нем) предназначена функция pthread_mutex_destroy.

В операционной системе Windows прототипы функций, предназначенных специально для работы с mutex-семафором, следующие:

HANDLE CreateMutex(SECURITY_ATTRIBUTES* MtxAttrs,

BOOL bInitialOwner, STR* Name);

HANDLE OpenMutex(DWORD DesiredAccess,

BOOL bInheritHandle, STR* Name);

BOOL ReleaseMutex(HANDLE hMutex).

В простейших случаях (и большинстве других) параметр MtxAttrs, являющийся указателем на структуру атрибутов защиты для семафора, не используется, при этом сам параметр задается значением NULL. Параметр bInitialOwner определяет, будет ли владеть нить – создатель семафора – этим семафором или он будет создан никому не принадлежащим. В первом случае значение этого параметра должно быть задано равным TRUE, во втором – равным FALSE. Параметр Name определяет имя mutex-семафора. Семафоры здесь могут быть именованные и неименованные. Для определения неименованных семафоров параметр Name задается со значением NULL. Отличное от него значение определяет имя именованного семафора. Ограничения на построение этого имени практически отсутствуют, за исключением того, что в его состав не должен входить символ '\'. Для открытия семафора используется параметр DesiredAccess, который задается любой комбинацией флагов MUTEX_ALL_ACCESS и SYNCHRONIZE. Первый из них обозначает, что заданы все возможные флаги доступа, а второй – что полученный хэндл можно будет использовать в любых функциях ожидания события, в частности функции WaitForSingleObject. Параметр bInheritHandle показывает, будет ли наследоваться хэндл семафора потомками данного процесса. Чтобы такое наследование происходило, следует этот параметр задавать равным TRUE. Функции создания и открытия mutex-семафора возвращают либо нулевое значение, свидетельствующее об ошибке (код которой получается немедленным применением функции GetLastError), либо выдаваемое для использования в программе значение хэндла семафора. Функция ReleaseMutex выдает значение FALSE, если данная нить не является владельцем семафора.

Для запроса владения семафора Windows использует общую функцию ожидания WaitForSingleObject, а для его закрытия - общую функцию CloseHandle.

Следующий пример, представленный программой в листинге 9.3.1, демонстрирует простейшее использование в Windows рассмотренных функций.

 

// использование семафора для согласования доступа к экрану

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <windows.h>

#include <process.h>

#define STACK_SIZE 4096

 

char a1[ ] = "Земную жизнь пройдя до половины";

char a2[ ] = "Я очутился в сумрачном лесу";

char a3[ ] = "Путь правый утеряв во тьме долины.";

char b1[ ] = "Семафоры предназначены для правильного взаимодействия";

char b2[ ] = "Между процессами и нитями";

char b3[ ] = "В современных мультизадачных операционных системах.";

HANDLE hthread, hmtx;

void main(void)

{

void procthread(void *arg); // прототип для процедуры нити

unsigned long threadid; // номер ID нити

 

hmtx=CreateMutex(NULL, FALSE, NULL);

if (hmtx= =NULL) {printf("Mutex=%d\n", hmtx); getchar();}

 

hthread=(HANDLE)_beginthreadNT(procthread, STACK_SIZE, NULL, NULL, 0,

&threadid);

if (hthread= =NULL) {printf("hthread=%d\n", hthread); getchar();}

while(TRUE) // Повторять пока не будет нажата комбинация клавиш Cntrl-C

{ WaitForSingleObject(hmtx, INFINITE);

printf("%s\n", a1);

Sleep(100);

printf("%s\n", a2);

Sleep(100);

printf("%s\n\n", a3);

Sleep(100);

ReleaseMutex(hmtx);

Sleep(100);

}

}

 

void procthread(void *arg) /* данные пишутся на экран */

{

while(TRUE) {

WaitForSingleObject(hmtx, INFINITE);

printf("%s\n", b1);

Sleep(100);

printf("%s\n", b2);

Sleep(100);

printf("%s\n\n", b3);

Sleep(100);

ReleaseMutex(hmtx);

Sleep(100);

}

}

Листинг 9.3.1. Программа для Windows

Программа задает функционирование двух нитей. Главная нить создает mutex-семафор, получает его хэндл в переменную hmtx, а затем создает (запускает на выполнение) нить на основе процедуры с именем procthread. Потом главная нить входит в цикл, который прекращается по нажатию любой клавиши клавиатуры. В этом цикле тремя отдельными операторами выводится три строки семантически связного текста, причем перед выводом этих строк запрашивается владение семафором с хэндлом hmtx, а после их вывода этот семафор освобождается. В процедуре, задающей функционирование нити, также тремя отдельными операторами выводятся три строки другого семантически связного текста, этот вывод повторяется в цикле с неограниченным числом повторений. Перед началом последовательности операторов вывода запрашивается владение тем же семафором (с хэндлом hmtx), что используется и в главной нити, а после вывода системной функцией этот семафор освобождается.

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

 

В операционной системе Unix функции для работы с mutex-семафором имеют прототипы:

int pthread_mutex_init(ptread_mutex_t* hmtx, ptread_mutexattr_* pattr);

int pthread_mutex_lock(ptread_mutex_t* hmtx);

int pthread_mutex_trylock(ptread_mutex_t* hmtx);

int pthread_mutex_unlock(ptread_mutex_t* hmtx);

int pthread_mutex_destroy(ptread_mutex_t* hmtx)

и описаны в заголовочном файле pthread.h. Во всех этих функциях в качестве первого аргумента используется указатель на хэндл семафора, что,в частности,позволяет получать от ОС значение хэндла в функции инициализации. В функции инициализации второй аргумент, задаваемый также указателем, определяет атрибуты для нового mutex-семафора. Функция pthread_mutex_trylock() позволяет опрашивать наличие владения семафора другой нитью (процессом) без блокировки опрашивающего процесса. При этом если заданный в вызове семафор занят, то функция вернет ненулевой код возврата. Как и в большинстве функций POSIX, значение кодов возврата у перечисленных функций, равное 0, обозначает успешное выполнение функции, а неравные нулю значения дают числовые значения кода ошибки их выполнения.

Следующий пример, представленный программой в листинге 9.3.2, демонстрирует решение описанной выше задачи для Unix.

 

// использование семафора для согласования доступа к экрану

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

#define TRUE 1

 

char a1[ ] = "Земную жизнь пройдя до половины";

char a2[ ] = "Я очутился в сумрачном лесу";

char a3[ ] = "Утратив правый путь во тьме долины.";

char b1[ ] = "Семафоры предназначены для организации";

char b2[ ] = "Согласованного взаимодействия между процессами и нитями";

char b3[ ] = "В современных многозадачных операционных системах.";

pthread_mutex_t hmtx;

 

void main(void)

{void* procthread(void *arg); /* прототипы для новой цепочки */

pthread_t tid; // tid

 

pthread_create(&tid, NULL, procthread, (void*)0);

pthread_mutex_init(&hmtx, NULL);

while(TRUE) // писать данные на экран до нажатия Cntl-C

{pthread_mutex_lock(&hmtx);

printf("%s\n", a1);

usleep(200000);

printf("%s\n", a2);

usleep(200000);

printf("%s\n\n", a3);

pthread_mutex_unlock(&hmtx);

usleep(200000);

}

pthread_mutex_destroy(&hmtx);

}

 

void* procthread(void* arg) // пишутся данные на экран

{ while(TRUE) {

pthread_mutex_lock(&hmtx);

printf("%s\n", b1);

usleep(200000);

printf("%s\n", b2);

usleep(200000);

printf("%s\n\n", b3);

pthread_mutex_unlock(&hmtx);

usleep(200000);

}

}

Листинг 9.3.2. Программа для Unix

 

Кроме рассмотренных функций работы с mutex-семафорами,в некоторых ОС присутствуют дополнительные функции, облегчающие программисту разработку программ с такими семафорами.

Дополнительные функции позволяют получить информацию о текущем состоянии семафора. Для OS/2 – это функция с прототипом

APIRET DosQueryMutexSem (HMTX hmtx, PID *ppid, TID *ptid,

ULONG* pulCount),

где возвращаемый параметр pulCount позволяет получить значение внутреннего счетчика вызовов семафора, который определяет разность между числом вызовов функции запроса владения (функция DosRequestMutexSem) и числом вызовов функции освобождения семафора (функция DosReleaseMutexSem). Если в момент выполнения запроса DosQueryMutexSem о состоянии семафора он свободен (никому не принадлежит), то значение этого счетчика равно нулю. В результате вызова этой функции может быть получен код возврата, равный ERROR_SEM_OWNER_DIED (числовое значение 105), в этом случае параметр pulCount возвращает значение счетчика вызовов на момент завершения нити, которая не освободила семафор. Возвращаемые параметры ppid и ptid позволяют получить информацию о текущем владельце семафора – процессе и нити, которым принадлежит этот семафор. Если код возврата равен ERROR_SEM_OWNER_DIED, эти указателя дают возможность получить идентификаторы процесса и нити, прекратившейся без освобождения семафора.

Функция запроса владения семафора может выполняться в программе и над семафором, которым уже владеет данная нить, блокировка нити при этом не возникает, но увеличивается на единицу значение счетчика вызовов семафора. Поэтому для освобождения семафора данная нить должна будет выполнить функцию освобождения семафора не один раз, а столько раз, сколько она делала запрос на владение (в Windows универсальной функцией WaitForSingleObject).

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

В Linux семафоры взаимоисключения предоставляют дополнительные возможности. Они связаны с попытками нити захватить семафор, уже принадлежащий ей. Эти возможности называют типами mutex семафоров. Имеются три таких типа: быстрый семафор, рекурсивный семафор и контролирующий семафор. Попытка захвата быстрого семафора приводит к блокировке нити, но такая же попытка для рекурсивного семафора не приводит к блокировке. При этом для освобождения mutex семафора оказывается нужным выполнить вызов функции pthread_mutex_unlock() столько раз, сколько перед этим выполнялось вызовов функции pthread_mutex_lock().

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

По умолчанию в Linux создается быстрый семафор. Для других типов следует использовать инициализацию семафора с явно задаваемыми атрибутами. С этой целью нужно создать переменную типа pthread_mutexattr_t и передать указатель на нее функции pthread_mutexattr_init(). Затем следует задать тип семафора с помощью вспомогательной функции pthread_mutexattr_setkind_np(). Последняя функция имеет два аргумента, первый из которых задается адресом на экземпляр инициализированной структуры pthread_mutexattr_t, а второй – одной из констант PTHREAD_MUTEX_RECURSIVE_NP и PTHREAD_MUTEX_ERRORCHECK_NP – для рекурсивного и контролирующего типа соответственно. После такой подготовки экземпляра типа pthread_mutexattr_t следует использовать указатель на него в качестве второго аргумента функции pthread_mutex_init(), а сам экземпляр объекта атрибутов затем удалить вызовом pthread_mutexattr_destroy().

Как неявно подсказывает префикс _NP, рекурсивные и контролирующие семафоры специфичны для Linux и не могут использоваться в программах, переносимых в другие варианты ОС Unix.




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


Дата добавления: 2014-01-05; Просмотров: 1043; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.092 сек.