Вопрос 7. Системные объекты ОС Windows для синхронизации параллельной работы потоков. Пример (фрагмент) программы использования одного из объектов синхронизации (по заданию комиссии).


Добавил:DMT
Дата создания:30 декабря 2007, 18:58
Дата обновления:9 января 2008, 23:54
Просмотров:18702 последний сегодня, 22:34
Комментариев: 2
Вопрос 7. Системные объекты ОС Windows для синхронизации параллельной работы потоков. Пример (фрагмент) программы использования одного из объектов синхронизации (по заданию комиссии).

Основными типами объектов синхронизации являются события ( events ), мьютексы ( mutexes ) и семафоры ( semaphores ).

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

Со­бытия могут быть мануальными ( manual ) и единичными ( single ), соответственно оповещающие о том что событие произошло либо множество потоков сразу, либо единственный поток, после чего событие сбрасывается системой автоматически. Тип события указывается при его создании.

Вызов

Назначение

CreateEvent OpenEvent SetEvent ResetEvent PulseEvent

Создает событие или открывает уже существующее событие

Открывает существующее событие

Устанавливает событие (переводит его в сигнальное состояние)

Сбрасывает событие

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

 

2. Мьютекс ( mutex , mutual exclusion — взаимное исклю­чение). Поток может получить мьютекс в собственное владение. При этом другие потоки не смогут завладеть мьютексом до тех пор, пока первый поток не освобо­дит его. Таким образом, в любой момент времени мьютекс может принадлежать только одному потоку, что исключает конфликты между потоками. Вместе с тем поток, являющийся владельцем мьютекса , может попытать­ся стать владельцем мьютекса повторно. Если поток присваивал себе мьютекс несколько раз, он обязан освободить его такое же количество раз и это расценивается как одно o бpaщение . Вызовы, пред­назначенные для работы с мьютексами , перечислены в таблице.

Мьютекс находится в сигнальном состоянии в случае, если он никому не принадлежит. Если мьютекс больше не нужен, вы можете освободить его при помощи вызо­ва ReleaseMutex .

Вызов

Предназначение

CreateMutex OpenMutex ReleaseMutex

Создает новый мьютекс или открывает уже существующий

Открывает существующий мьютекс

Освобождает мьютекс и делает его доступным для других потоков

 

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

Семафор находится в сигнальном состоянии, если его счетчик боль­ше нуля. Чтобы освободить занятый вами ранее семафор, используйте вызов ReleaseSemaphore . При этом счетчик семафора будет увеличен на единицу.

Вызов

Назначение

CreateSemaphore OpenSemaphore ReleaseSemaphore

Создает новый семафор или открывает существующий

Открывает существующий семафор

Добавляет некоторое значение (обычно 1) к счетчику семафора, делая его доступным для большего количества потоков

 

Другие объекты синхронизации

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

Чтобы использовать критическую секцию, необходимо создать в глобальной памяти переменную CRITICAL_SECTION. После этого программа должна один раз обратиться к функции InitializeCriticalSection . В начале участка кода, доступ к которому требуется ограничить, необходимо разместить обращение к функции EnterCriticalSection . В конце критического участка кода следует разместить обращение к функции LeaveCriticalSection .

Блокированные переменные. Windows позволяет организовать работу программы таким образом, чтобы к некоторым переменным в любой момент времени мог обращаться только один поток. Для этого служат так называемые блокированные вызовы. Например, вызов InternetlockedIncrement .

Таймер синхронизации ( waitable timer ) является еще одним способом вызова функции АРС.

Оповещение об изменении ( change notification ) это специальный объект, который переходит в сигнальное состояние в случае, если содержимое дискового каталога изменяется. Чтобы создать оповещение об изменении, необходимо обратиться к функции F i ndFirstChangeNotification .

Дискриптор консоли переходит в сигнальное состояние в случае, если в стандартном потоке ввода находятся данные, которые необходимо прочитать.

 

Пример (фрагмент) программы использования одного из объектов синхронизации (по заданию комиссии).

Листинг 1. Программа, иллюстрирующая работу с единичны­ми событиями .

Код на C++
  1. #include <windows.h>
  2. #include <iostream.h>
  3.  
  4. void main(int argc, char *argv[])
  5. {
  6. // создаем или открываем имеющееся
  7. HANDLE ev=CreateEvent(NULL, FALSE, FALSE, "nt5bbevent");
  8. if (argc==1)
  9. {
  10. cout<<"Подождите событие!\n";
  11. cout.flush();
  12. WaitForSingleObject(ev, INFINITE);
  13. cout<<"Событие произошло!\n";
  14. cout.flush();
  15. }
  16. else
  17. {
  18. cout<<"Установка события\n";
  19. SetEvent(ev);
  20. }
  21. CloseHandle(ev);
  22. }
При использовании обязательна ссылка на http://DMTSoft.ru

Листинг 2. Тестирование работы с мьютексом .

Код на C++
  1.  
  2. #include <windows.h>
  3. #include <iostream.h>
  4.  
  5. void main()
  6. {
  7. HANDLE mtx=CreateMutex(NULL, FALSE, "NT5BBMTX");
  8. cout<<"Проверить мьютекс\n";
  9. cout.flush();
  10. WaitForSingleObject(mtx, INFINITE);
  11. cout<<"Мьютекс получен!\n";
  12. cout.flush();
  13. MessageBox(NULL,"Программа захватила мьютекс", "MUTEX", MB_OK);
  14. ReleaseMutex(mtx);
  15. CloseHandle(mtx);
  16. }
При использовании обязательна ссылка на http://DMTSoft.ru

Листинг 3. Демонстрация работы семафора .

Код на C++
  1. #include <windows.h>
  2. #include <iostream.h>
  3.  
  4. void main()
  5. {
  6. HANDLE sem=CreateSemaphore(NULL, 3, 3, "nt5bbsem"); //семафор со счетчиком = 3
  7. cout<<"Проверить состояние семафора\n";
  8. cout.flush();
  9. WaitForSingleObject(sem, INFINITE);
  10. cout<<"Получен доступ к семафору\n";
  11. cout.flush();
  12. MessageBox(NULL, "Получен доступ к семафору", "Semaphore", MB_OK);
  13. ReleaseSemaphore(sem, 1, NULL);
  14. CloseHandle(sem);
  15. }
При использовании обязательна ссылка на http://DMTSoft.ru
up

Комментарии для "Вопрос 7. Системные объекты ОС Windows для синхронизации параллельной работы потоков. Пример (фрагмент) программы использования одного из объектов синхронизации (по заданию комиссии)."


Пользователь: ruslan
Сообщений: 23
Статус: Незримый
Зарегистрирован:
5 января 2008, 2:42
Был:29 января 2008, 21:23
ruslan
smsup
Дата: 10 января 2008, 1:02 Сообщение № 1
События. Возможно, что при выполнении двух потоков один из них должен ожидать возникновения некоторой ситуации, например, окончания вычисления другим потоком некоторой переменной. Для того чтобы сообщить потокам или процессам о возникновении определенной ситуации, используются события. Создание объекта события производится с помощью функции

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpAttr, BOOL Manual, BOOL Initial, LPSTR lpszName),


где параметр lpAttr является указателем на структуру атрибутов доступа. Если также атрибуты не используются, то lpAttr = NULL. Значение параметра Manual определяет, что необходимо сделать с объектом события после того, как событие произошло. Если это значение равно TRUE (не нуль), то событие может быть сброшено только при помощи вызова функции ResetEvent(). В противном случае событие сбрасывается автоматически после того, как к нему получает доступ какой-нибудь ожидающий этого события процесс. Значение Initial задает начальное состояние события. Если оно равно TRUE, то событие сразу устанавливается. Если оно равно FALSE, то событие инициализируется как сброшенное. Параметр lpszName является указателем на строку, содержащую имя события.

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

События представляют собой глобальные системные объекты, доступные всем процессам в системе. Поэтому, если разные процессы открывают объект события, задавая одно и то же имя, то используется один и тот же системный объект. Параметр lpszName может быть равен NULL, и в этом случае объект события определяется как локальный для данного процесса.

Функция CreateEvent() возвращает дескриптор созданного объекта события либо NULL при возникновении ошибки.


Семафор представляет собой счетчик, который считается свободным, если значение счетчика больше нуля, и занятым при нулевом значении. При создании семафора задаются его максимально допустимое и начальное состояния. Ожидающие функции WaitFor... уменьшают значение свободного семафора на 1, если счетчик ненулевой, или переходят в режим ожидания до тех пор пока кто-либо не увеличит значение семафора. Увеличение счетчика осуществляется функцией ReleaseSemaphore.

Создать объект семафор можно с помощью функции

HANDLE CreateSemaphore (LPSECURITY_ATTRIBUTES lpAttr, LONG InitialCount, LONG MaxCount, LPSTR lpszName),


где:
lpAttr – указатель на структуру, определяющую атрибуты доступа (например, SEMAPHORE_ALL_ACCESS), он равен NULL, если атрибуты доступа не используются;
InitialCount – начальное значение счетчика семафора; если это значение равно нулю, то с самого начала все объекты, ожидающие семафор, будут блокированы, пока семафор не будет освобожден;
MaxCount – количество задач, которые могут иметь одновременный доступ к объекту, с которым связан семафор; иными словами, MaxCount – это максимальное значение счетчика семафора; если MaxCount = 1, то семафор называется мьютексом;
lpszName – указатель на строку, задающую имя семафора; если два различных процесса открывают семафор, задавая одно и то же имя, то используется один и тот же семафор, следовательно, в этом случае семафор может служить способом синхронизации процессов; если lpszName = NULL, то семафор создается как локальный для данного процесса.

Функция CreateSemaphore() при успешном завершении возвращает дескриптор созданного семафора, а при возникновении ошибки – NULL.

Операцию увеличения значения счетчика выполняет функция BOOL ReleaseSemaphore(HANDLE hSema, LONG Count, LPLONG lpPrevCount). Эта функция «освобождает» семафор, позволяя использовать его другому процессу или потоку. Параметр hSema является дескриптором семафора. Параметр Count равен значению, на которое увеличивается счетчик семафора. Обычно оно равно единице. Параметр lpPrevCount равен указателю на переменную типа LONG, в которую будет записано предыдущее значение счетчика семафора. Функция возвращает ненулевое значение при успешном завершении и нуль – при возникновении ошибки.


Мьютексы – объекты исключительного владения, могут быть использованы в одно время не более чем одним потоком. Мьютексы могут быть использованы для межпроцессного взаимодействия. Мьютексы также называют бинарными семафорами, т.к. счетчик такого семафора принимает значения 0 и 1.
Создание мьютекса осуществляется функцией

HANDLE CreateMutex (LPSECURITY_ATTRIBUTES lpAddr, BOOL InitialCount, LPSTR lpszName),


параметры которой имеют те же значения, что и параметры создания семафора. Эта функция отличается параметром InitialCount (равным false или true).

Для захвата мьютекса используется ожидающая функция WaitFor..., а для освобождения - функция ReleaseMutex. CreateMutex можно указать, чтобы он создавался сразу в занятом состоянии.


Таймер синхронизации. Одним из способов вызова функции АРС (Asynchronous Procedure Call) является таймер синхронизации (waitable timer). Для создания нового или открытия уже существующего тай¬мера синхронизации можно использовать функции CreateWaitableTimer и OpenWaitableTimer. Таймер переходит в сигнальное состояние в момент, когда истекает соответствующий ему временной интервал. Таймер синхронизации можно настроить таким образом, что в момент истечения его периода времени происхо¬дит вызов функции АРС в некотором потоке. Как и в других подобных случаях, вызов АРС не сработает до тех пор, пока целевой поток не перейдет в насторо¬женное состояние.

HANDLE CreateWaitableTimer(LPSECURITY_ATTRIBUTES lpTimerAttributes,
BOOL bManualReset, LPCTSTR lpTimerName)


lpTimerAttributes – указатель на структуру, определяющую атрибуты доступа;
bManualReset – если TRUE, то таймер является сбрасываемым вручную уведомляющим таймером, иначе получится синхронизирующий таймер;
lpTimerName – имя объекта таймера.

Вызовы CreateWaitableTimer и OpenWaitableTimer возвращают дескриптор таймера. Чтобы начать отсчет времени таймером, необходимо обратиться к функции SetWaitableTimer.

BOOL SetWaitableTimer(HANDLE hTimer, const LARGE_INTEGER* pDueTime, LONG lPeriod, PTIMERAPCROUTINE pfnCompletionRoutine, LPVOID lpArgToCompletionRoutine, BOOL fResume)


Период времени указывается как значение типа LARGE_INTEGER, равное количеству единиц времени, каждая из которых равна 100 наносекундам. Если указан положительный период времени, считается, что это абсолютное время. Отрицательные значения обозначают относительное время. С помощью параметра lPeriod можно настроить таймер таким образом, чтобы он сработал только один раз (lPeriod = 0), а можно задать срабатывание тайсмера через равные промежутки времени.

Когда период времени истекает, таймер переходит в сигнальное состояние. Можно настроить таймер таким образом, чтобы при переходе его в сигнальное состояние автоматически выполнялся вызов функции АРС. Для этого нужно передать в параметре pfnCompletionRoutine указатель на эту процедуру, а в параметре lpArgToCompletionRoutine аргументы процедуры асинхронного вызова. Остановить таймер можно при помощи вызова CancelWaitableTimer.


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

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

Существуют четыре основных функции для работы с критическими секциями; перед использованием критическая секция должна быть инициализирована с помощью функции InitializeCriticalSection. Если секция занимается другим потоком на небольшое время, то такой цикл позволяет дождаться ее освобождения, не переходя в режим ядра. Для этого предназначены функции: InitializeCriticalSectionAndSpinCount и SetCriticalSectionSpinCount. Они позволяют задавать число опросов состояния занятой критической секции перед переходом в режим ядра для ожидания.

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

Работа с критическими секциями сводится к двум основным функциям: функция EnterCriticalSection, которая соответствует входу в критическую секцию, при необходимости с ожиданием, не ограниченным по времени; и функция LeaveCriticalSection, которая соответствует выходу из этой секции, возможно с пробуждением потоков, ожидающих ее освобождения. При этом система исключает вход в критическую секцию всех остальных потоков процесса, в то время как поток, уже вошедший в данную секцию, может входить в нее рекурсивно - надо лишь, чтобы число выходов из секции соответствовало числу входов.

Оповещение об изменениях (change notification) – это специальный объект, который переходит в сигнальное состояние в случае, если содержимое дискового каталога изменяется. Чтобы создать оповещение об изменении, необходимо обратиться к функции FindFirstChangeNotification.

HANDLE FindFirstChangeNotification(LPCTSTR lpPathName, BOOL bWatchSubtree, DWORD dwNotifyFilter);


lpPathName – имя каталога;
bWatchSubtree – флаг указывает на то, что поиск изменений будет осуществляться не только в указанном каталоге, но и во всех его подкаталогах;
dwNotifyFilter – флаг определяет набор событий, которым будет уделять внимание оповещение об изменениях таких, как переименование директории, файла или изменение размера файла.

Если происходит любое из указанных изменений, дескриптор переходит в сигнальное состояние. Переход дескриптора в сигнальное состояние можно обнаружить при помощи WaitForSingleObject.

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

Дискриптор консоли переходит в сигнальное состояние в случае, если в стандартном потоке ввода находятся данные, которые необходимо прочитать. Стандартный дискриптор ввода можно получить несколькими способами. Например, можно обратиться к функции CreateFile и передать ей в качестве имени файла ключевое слово CONIN$ или вызвать функцию GetStdHandlе. Используя подобную технологию, можно организовать работу программы таким образом, чтобы она реагировала на поступление управляющих команд как из последовательного порта, так и от консоли.

Используя подобную технологию, можно организовать работу программы таким образом, чтобы она реагировала на поступление управляющих команд как из последовательного порта, так и от консоли. Подобный подход легко реализуется при помощи функции WaitForMultipleObjects.
Пользователь: ruslan
Сообщений: 23
Статус: Незримый
Зарегистрирован:
5 января 2008, 2:42
Был:29 января 2008, 21:23
ruslan
smsup
Дата: 27 января 2008, 0:47 Сообщение № 2
Листинг 4. Демонстрация работы с критическими секциями.

Код на C++
  1. // глобальная переменная
  2. CRITICAL_SECTION CriticalSection;
  3. void main()
  4. {
  5. ...
  6. // Инициализация критической секции с установлением числа опросов
  7. if (!InitializeCriticalSectionAndSpinCount(&CriticalSection, 0x800))
  8. return;
  9. ...
  10. // Освобождаем ресурсы, используемые объектом критической секции
  11. DeleteCriticalSection(&CriticalSection)
  12. }
  13. DWORD WINAPI ThreadProc( LPVOID lpParameter )
  14. {
  15. ...
  16. // Вход в критическую секцию
  17. EnterCriticalSection(&CriticalSection);
  18. // здесь расположен критический участок кода
  19. // выход из критической секции
  20. LeaveCriticalSection(&CriticalSection);
  21. ...
  22. }
  23.  
При использовании обязательна ссылка на http://DMTSoft.ru