Студопедия

КАТЕГОРИИ:


Архитектура-(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)

Семафоры событий




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

Прежде всего необходимо иметь средства создания event-семафоров. Для создания их в операционных системах OS/2 и Windows, предназначены соответственно функции с именами DosCreateEventSem и CreateEvent. Для открытия доступа к семафорам событий имеются функции DosOpenEventSem и OpenEvent. Напомним еще раз, что роль семафоров событий описательно близка к сигналам, посылаемым заинтересованным нитям для продолжения их действий, приостановленных до этого сигнала. Еще более описательно начинающие могут представлять себе действие событий операционной системы как звонок будильника, по которому просыпаются все запланировавшие проснуться по нему.

Для включения "механизма звонка будильника" – включения сигнала события служат функции DosPostEventSem и SetEvent. Чтобы такой сигнал-будильник (event-семафор) можно было использовать повторно для ожидания другого события, нужно иметь средство отключать такой семафор-будильник. Для принудительного выключения сигнала от event-семафора служат функции сброса, которые в операционных системах OS/2 и Windows, называются соответственно DosResetEventSem и ResetEvent. Ожидание события, связанного с event-семафором, в OS/2 выполняет функция DosWaitEventSem, а в Windows – вездесущая функция WaitForSingleObject, которая на этот раз должна в качестве аргумента использовать хэндл именно семафора событий.

Перед использованием семафора событий, созданного другим процессом – для получения доступа к нему – необходимо выполнить функцию открытия доступа к event-семафору. Для этих целей предназначены функции DosOpenEventSem и OpenEvent.

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

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

HANDLE CreateEvent(SECURITY_ATTRIBUTES* pattributes,

BOOL ManualReset, BOOL fState, STR* pName);

BOOL SetEvent(HANDLE hEvent);

BOOL ResetEvent(HANDLE hEvent);

HANDLE OpenEvent(DWORD access, BOOL inherit, STR* pName);

BOOL PulseEvent(HANDLE hEvent).

В этих функциях параметр hEvent обозначает хэндл семафора событий, параметр pName – указатель на имя семафора, которое в Windows может быть любым текстом, не включающем символа '\'. Семафоры для использования несколькими процессами в Windows должны быть обязательно именованными, неименованные допустимы только в пределах одного процесса. Неименованные семафоры событий создаются использованием нулевого указателя вместо параметра pName в функции создания такого семафора. Параметр pattributes является указателем на структуру атрибутов защиты, отличных от атрибутов по умолчанию, и в большинстве случаев задается нулевым указателем, что приводит к использованию атрибутов по умолчанию. Параметр fState задает начальное состояние семафора события и его начальное состояние FALSE отвечает отсутствию сигнала о событии, а состояние TRUE задает начальный сигнал о событии.

Параметр inherit определяет, будут ли дочерние процессы наследовать данный открываемый семафор события (если TRUE, то будут). Параметр access в функции открытия семафора событий задает флаги доступа и может быть комбинацией следующих значений: EVENT_ALL_ACCESS, EVENT_MODIFY_STATE, SYNCHRONIZE. Первый флаг обозначает, что заданы все возможные типы доступа (и поэтому другие флаги программисту использовать не имеет смысла), флаг EVENT_MODIFY_STATE определяет, что после открытия хэндл можно будет использовать для функций SetEvent и ResetEvent, а последний из перечисленных флаг – что этот хэндл можно использовать в любых функциях ожидания событий.

Наиболее интересным по последствиям является применение параметра ManualReset в функции создания семафора события. С помощью его можно выбрать один из режимов работы семафора события, называемых ручной и автоматический. Ручному режиму соответствует значение TRUE параметра, а автоматическому – значение FALSE. Ручной режим отвечает необходимости использовать функцию сброса сигнала о событии для последующего использования того же семафора (функцию ResetEvent). В автоматическом режиме сигнал от семафора событий сбрасывается непосредственно при выходе из функции ожидания события от такого семафора. При этом только одна из нитей, ожидающих события, если таких нитей более одной, выходит из ожидания и продолжает свою работу. Все остальные нити, ожидающие такой семафор событий с автоматическим сбросом, по-прежнему остаются заблокированными – по крайней мере, до появления очередного сигнала от события.

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

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

 

#include <stdio.h>

#include <windows.h>

#include <process.h>

#define STACKSIZE 4096

#define BUFFLEN 80 // длина входного буфepа

 

char name[BUFFLEN]; // буфер для имени

char age[BUFFLEN]; // буфер для возраста

HANDLE hthread, hevent;

 

int main()

{ void keyboard(void *arg); // прототип для новой нити

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

 

hevent=CreateEvent(NULL, TRUE,// флаг manual reset: будет нужен ResetEvent

FALSE, NULL);// event без имени

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

hthread=(HANDLE)_beginthreadNT(keyboard, STACKSIZE, NULL, NULL, 0,

&threadID);

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

 

printf("Введите ваше имя: ");

WaitForSingleObject(hevent, INFINITE);

printf("Введите ваш возраст: ");

ResetEvent(hevent);

WaitForSingleObject(hevent, INFINITE);

printf("Поздравляю Вас %s в возрасте %s лет\n", name, age);

getchar();

CloseHandle(hevent);

return 0;

}

 

void keyboard(void *arg)

{ gets(name);

SetEvent(hevent);

gets(age);

SetEvent(hevent);

}

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

 

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

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

Главная нить, изобразив приглашения для ввода с консоли на экране, обращается к функции WaitForSingleObject, которая приостанавливает эту нить до тех пор, пока с помощью event-семафора вспомогательная нить не просигналит о событии. Тогда главная нить использует данные, введенные вспомогательной нитью, и выдаст результат их обработки (следующее приглашение) на экран. Сразу после этого она сбрасывает сигнал события функцией ResetEvent, чтобы дать вспомогательной нити тем же способом, как и раньше, сообщать о событии – готовности очередной строки вводимого текста.

В программе параметром FALSE при создании семафора события в функции CreateEvent указано, что начальное состояние отвечает отсутствию сигнала о событии. Иное значение этого параметра привело бы в данной программе к тому, что в главной нити не происходила бы приостановка на ожидании события и эта нить выводила на экран значение символьного массива, в который еще не введены данные, что резко отразилось бы на поведении программы. Для ясного осознания влияния семафора на процесс выполнения программы рекомендуется рассматриваемую программу запустить на выполнение с помощью системы разработки, а затем модифицировать, удалив вызовы функций ожидания события, и запустить на выполнение снова. Сравнение поведения двух таких вариантов программы должно помочь ясно осознать роль семафоров событий в подобных программах.

 

В операционной системе Unix нет даже термина «семафор событий», но в ней имеются универсальные семафоры, даже более мощные, чем считающие абстрактные семафоры Дейкстры, и еще одна интересная вариация идеи семафора, названная условными переменными. В основном условные переменные соответствуют семафорам событий рассмотренных выше операционных систем и особенно семафорам событий с автоматическим режимом работы. Условные переменные используются для того, чтобы заблокировать нити до выполнения определенных условий, а момент выполнения такого условия можно рассматривать как событие. Для создания условной переменной используется системная функция с именем pthread_cond_init. Она имеет прототип

int pthread_cond_init(pthread_cond_t * pcond, pthread_condattr_t * pcond_attr),

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

Для задания срабатывания условной переменной, т.е. появления события выполнения условия этой переменной, существует функция pthread_cond_signal с прототипом

int pthread_cond_signal(pthread_cond_t * cond).

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

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

int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex).

Эта функция для своего применения требует не только созданной ранее условной переменной, указываемой аргументом cond, но и созданного ранее семафора взаимоисключения, который здесь указывается аргументом mutex (адресом mutex-семафора). Причем перед использованием для данных целей – использованием вместе с условной переменной – mutex-семафор должен быть первоначально захвачен во владение той же нитью, которая вызывает ожидание условной переменной. Функция ожидания условной переменной (срабатывания условной переменной) блокирует нить до тех пор, пока не появится сигнал установки этой переменной (от функции pthread_cond_signal). Как только такой сигнал появится, происходит разблокировка нити, сброс сигнала от условной переменной, и тут же внутри системной процедуры pthread_cond_wait() происходит захват во владение mutex-семафора, используемого этой процедурой. Причем из нескольких нитей, использующих вызов ожидания срабатывания условной переменной, только в одной удается захватить во владение этот mutex-семафор, именно той, которая вызовом pthread_mutex_lock() перед ожиданием условной переменной уже захватывала его во владение. Остальные нити продолжают оставаться приостановленными. Эти приостановленные нити ждут теперь очередного сигнала от условной переменной, т.е. нового выполнения функции pthread_cond_signal(). Заметим, что внутренний сброс условной переменной во внутренней процедуре pthread_cond_wait() как раз аналогичен автоматическому режиму семафора событий в Windows, но тут используемый mutex-семафор присутствует и функционирует явно.

Введение в стандартный набор системных функций не только семафоров взаимоисключении и семафоров событий с ручным сбросом, но и конструкций автоматического режима семафоров событий и условных переменных обусловлено недостаточностью первых двух типов для всего многообразия использования семафоров. Недостаток mutex-семафоров был отмечен ранее, обычные семафоры событий являются другой крайностью относительно абстрактных семафоров – они пробуждают все нити, ожидающие события. Ни те, ни другие не позволяют непосѐедственно воспроизвести операции P и V для семафоров, когда эти0операции выполняются не в одном, а0в0разлиїных процессах. Поэтому и появилиёь условные переменные в Unix и автоматический режим семафоров событий в Windows, и эти конструкции могут быть использованы для реализации операций P и V в указанных ситуациях.

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

int pthread_cond_destroy(pthread_cond_t * cond).

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

Для смыслового эквивалента ручного режима семафоров событий Unix содержит функцию с именем pthread_cond_broadcast, которая разблокирует все нити, которые ожидают события срабатывания условной переменной. Эта функция имеет прототип

int pthread_cond_broadcast(pthread_cond_t * cond)).

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

 

#include <stdio.h>

#define TRUE 1

#include <pthread.h>

#define BUFFLEN 80 // длина входного буфepа

char name[BUFFLEN]; // буфер для имени

char age[BUFFLEN]; // буфер для позиции

 

pthread_cond_t hevent;

pthread_mutex_t hmtx;

 

void main()

{ int rc;

void* keyboard(void *arg); // прототип для новой нити

pthread_t tid;

rc=pthread_create(&tid, NULL, keyboard, (void*)0); // запуск нити клавиатуры

pthread_cond_init(&hevent, NULL);

pthread_mutex_init(&hmtx, NULL);

printf("Введите ваше имя: ");

pthread_cond_wait(&hevent, &hmtx);

printf("Введите ваш возраст: ");

pthread_cond_wait(&hevent, &hmtx);

printf("Поздравляю Вас %s в возрасте %s лет\n", name, age);

pthread_cond_destroy(&hevent);

pthread_mutex_destroy(&hmtx);

}

 

void* keyboard(void* arg)

{ gets(name);

pthread_cond_signal(&hevent);

gets(age);

pthread_cond_signal(&hevent);

}

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

 

Заметим, что в этой простой задаче можно было бы использовать вместо функции pthread_cond_signal() функцию pthread_cond_broadcast(). В данном простейшем примере установки события ожидает лишь одна нить, и поэтому нет особой необходимости использовать взаимную блокировку нескольких нитей, зависящих от общего события. В более общем примере условные переменные используют для построения сигнальных переменных. Такие переменные своим значением определяют, следует или нет выполнять какой-то участок программы. К сожалению, простейшая проверка сигнальной переменной, выполняемая непосредственно средствами языка программирования, приводит к совершенно нерациональному расходованию компьютерных ресурсов, особенно досадному при многопоточном построении процессов.

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

Основная часть такой программы, существенная для данного обсуждения, будет иметь вид

#include <stdio.h>

#include <pthread.h>

int kflag=0;

pthread_mutex_t hmtx;

void* proc(void* arg)

{ int cur_kflag;

while(1) {

pthread_mutex_lock(&hmtx);

cur_kflag=kflag;

pthread_mutex_unlock(&hmtx);

if (cur_kflag) any_do();

}

void setkflag(int val) {

pthread_mutex_lock(&hmtx);

kflag=val;

pthread_mutex_unlock(&hmtx);

}

где в остальной части программы предполагается создание исключающего семафора с хэндлом hmtx, запуск некоторого числа нитей на основе процедуры proc и периодическое обращение к процедуре setkflag для разрешения и запрещения выполнения внутренних процедур any_do (). Заметим, что неизбежное использование mutex семафора и вызовов pthread_mutex_lock(), pthread_mutex_unlock() следует из общности ресурса kflag для параллельно выполняемых нитей. В данной реализации будет происходить многократная проверка ключа cur _ kflag при его нулевом значении, причем каждая из нитей, делающих это, будет заниматься только доступом к текущему значению ключа и проверкой, напрасно расходуя время процессора.

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

 

#include <stdio.h>

#include <pthread.h>

int kflag=0;

pthread_mutex_t hmtx;

pthread_cond_t hev;

 

void* proc(void* arg)

{while(1) {

pthread_mutex_lock(&hmtx);

while (!kflag) pthread_cond_wait(&hev, &hmtx);

pthread_mutex_unlock(&hmtx);

any_do();

}

void setkflag(int val) {

pthread_mutex_lock(&hmtx);

kflag=val;

pthread_cond_signal(&hev);

pthread_mutex_unlock(&hmtx);

}

 

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

Особенностью условных переменных в Unix является то, что сигнал, посылаемый такой переменной, действует только в том случае, если его в этот момент ожидает какая-то нить. Если же этот сигнал был послан в отсутствие ожидающей нити, а позже некоторая нить запросила ожидание этого же сигнала, то она будет заблокирована до момента следующей выдачи сигнала. Тем самым, условная переменная Unix все же заметно отличается от семафоров событий в Windows.

 




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


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


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



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




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