Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
АОПИ. Старое / АОПИ. Глава 2. Конспекты (02_04_19).rtf
Скачиваний:
65
Добавлен:
10.09.2019
Размер:
363.46 Кб
Скачать

Функция открытия существующего именованного объекта мьютекса

HANDLE OpenMutex(

IN DWORD dwDesiredAccess,

IN BOOL bInheritHandle,

IN LPCSTR Name

);

Параметры и описание:

(1) dwDesiredAccess — определяет доступ к объекту. 2 варианта: MUTEX_ALL_ACCESS (позволяет выполнять все действия по отношению к объекту мьютекса), SYNCHRONIZE (в потоке можно использовать события функций ожидания).

(2) bInheritHandle — определяет наследование. TRUE — дочерние процессы наследуют дескриптор. FALSE — не наследуют.

(3) Name — определяет имя мьютекса.

Возвращаемое значение.

Если функция завершилась успешно, она возвращает дескриптор объекта мьютекса, в противном случае — возвращает NULL.

Для получения доступа к разделяемому ресурсу поток вызывает одну из wait-функций и передает ей дескриптор мьютекса, охраняющего этот ресурс. Wait-функция проверяет у мьютекса идентификатор потока-владельца. Если идентификатор равен нулю, то ресурс свободен и вызывающий поток может продолжить выполнение. В этом случае перед возвратом из wаit-функции идентификатор потока-владельца в мьютексе становится равным идентификатору вызывающего потока, а счетчику рекурсии присваивается единичное значение.

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

Функция освобождения владельца указанного объекта мьютекса

BOOL ReleaseMutex(HANDLE hMutex);

Краткое описание.

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

Функция ReleaseMutex завершается ошибкой, если вызывающий поток не владеет объектом мьютекса.

Возвращаемое значение:

Если функция завершается успешно, возвращаемое значение отлично от нуля (bool: true), иначе равно нулю (bool: false).

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

А что будет, если поток, владеющий мьютексом, завершится, не успев его освободить? В таком случае система считает, что произошел отказ от мьютекса, и автоматически переводит его в свободное состояние. Если этот мьютекс ждут другие потоки, то система выбирает один из них и позволяет ему захватить мьютекс. Тогда wаit-функция возвращает потоку значение WAIT_ABANDONED вместо WAIT_OBJECT_0. Это позволяет понять, что мьютекс освобожден некорректно.

Обычно программы после возврата из wаit-функции специально не проверяют возвращаемое значение на WAIТ_ABANDONED, поскольку такое завершение потоков происходит очень редко.

Пример (C).

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

В следующем примере функция CreateMutex используется для создания объекта мьютекса, а функция CreateThread — для создания рабочих потоков. Когда поток этого процесса выполняет запись в базу данных, он сначала запрашивает владение мьютексом с помощью функции WaitForSingleObject. Если поток получает право собственности на мьютекс, он записывает в базу данных, а затем освобождает свое право собственности на мьютекс, используя функцию ReleaseMutex.

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

#include <stdio.h>

#include <locale.h>

#include <windows.h>

#define THREADCOUNT 5

HANDLE ghMutex;

DWORD WINAPI WriteToDatabase(LPVOID);

int main() {

setlocale(LC_ALL, "Russian");

HANDLE aThread[THREADCOUNT];

DWORD ThreadID;

int i;

/// Создаем мьютекс без первоначального владельца

ghMutex = CreateMutex(NULL, FALSE, NULL); /// Безымянный мьютекс

if (ghMutex == NULL) { /// Если ошибка

printf("CreateMutex ошибка: %d\n", GetLastError());

return 1;

}

/// Создаем рабочие потоки

for (i = 0; i < THREADCOUNT; i++) {

aThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) WriteToDatabase, NULL, 0, &ThreadID);

if ( aThread[i] == NULL ) { /// Если ошибка

printf("CreateThread ошибка: %d\n", GetLastError());

return 1;

}

}

/// Ждем завершения всех потоков

WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

/// Закрываем дескрипторы потоков

for (i = 0; i < THREADCOUNT; i++)

CloseHandle(aThread[i]);

/// Закрываем дескриптор мьютекса

CloseHandle(ghMutex);

return 0;

}

DWORD WINAPI WriteToDatabase(LPVOID lpParam) {

DWORD dwCount=0, dwWaitResult;

/// Попробуем получить право собственности на мьютекс

while (dwCount < 2) { /// От каждого потока по 2 раза пробуем

/// В итоге: THREADCOUNT * 2 = количество сообщений

dwWaitResult = WaitForSingleObject(ghMutex, INFINITE);

switch (dwWaitResult) {

/// Поток получил право собственности на мьютекс

case WAIT_OBJECT_0:

/// Пишем в базу данных что-то

printf("Поток %d пишет в базу данных\n", GetCurrentThreadId());

dwCount++;

/// Увеличиваем счетчик (мы отработали)

/// Освободим владельца мьютекса

if (!ReleaseMutex(ghMutex))

/// Если ошибка, то

printf("Ошибка! Неверный дескриптор");

break;

/// Поток получил право собственности на заброшенный мьютекс

/// База данных находится в неопределенном состоянии

case WAIT_ABANDONED:

return FALSE;

}

}

return TRUE;

}

Домашнее задание.

Узнать про критическую секцию.

Узнать про спин-блокировку.

Критическая секция — участок исполняемого кода программы, в котором производится доступ к общему ресурсу (данным или устройству), который не должен быть одновременно использован более чем одним потоком выполнения. Критическая секция выполняет те же задачи, что и мьютекс. При нахождении в критической секции двух (или более) потоков возникает состояние «гонки» («состязания»). Для избежания данной ситуации необходимо выполнение четырех условий:

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

2. В программе не должно быть предположений о скорости или количестве процессоров.

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

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

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

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

CRITICAL_SECTION csFlag;

// объявление критической секции

...

InitializeCriticalSection(&csFlag);

// инициализация критической секции

...

EnterCriticalSection(&csFlag);

// функция входа в критическую секция

...

LeaveCriticalSection(&csFlag);

// функция выхода из критической секции

...

DeleteCriticalSection(&csFlag);

// Удаление (деинициализация) критической секции

Пример (C++).

#include <iostream>

#include <windows.h>

#include <process.h>

#define MAX_ARRAY 5

using namespace std;

void EmptyArray(void *);

void PrintArray(void *);

void FullArray(void *);

/// Глобальное объявление критической секции

CRITICAL_SECTION critsect;

/// Глобальное объявление массива

int Array[MAX_ARRAY];

/// Глобальное объявление вспомогательного счетчика

int iCount = 0;

int main() {

/// Инициализация критической секции

InitializeCriticalSection(&critsect);

/// _beginthread создает поток

cout << "Thread 1 START\n";

if (_beginthread(EmptyArray, 0, NULL) == -1)

cout << "Error begin thread 1\n";

cout << "Thread 1 STOP, Thread 2 START\n";

if (_beginthread(PrintArray, 0, NULL) == -1)

cout << "Error begin thread 2\n";

while (iCount < 2) continue; /// Ждем вывод

cout << "Thread 2 STOP, Thread 3 START\n";

if (_beginthread(FullArray, 0, NULL) == -1)

cout << "Error begin thread 3\n";

cout << "Thread 3 STOP, Thread 4 START\n";

if (_beginthread(PrintArray, 0, NULL) == -1)

cout << "Error begin thread 4\n";

while (iCount < 4) continue; /// Ждем вывод

cout << "Thread 4 STOP, Thread 5 START\n";

if (_beginthread(EmptyArray, 0, NULL) == -1)

cout << "Error begin thread 5\n";

cout << "Thread 5 STOP, Thread 6 START\n";

if (_beginthread(PrintArray, 0, NULL) == -1)

cout << "Error begin thread 6\n";

while (iCount < 6) continue; /// Ждем вывод

cout << "Thread 6 STOP\n";

/// Ждем, пока все шесть потоков не отработают

while (iCount <= MAX_ARRAY) continue;

cin.get(); /// Ввод символа — аналог паузы

/// Деинициализация критической секции

DeleteCriticalSection(&critsect);

return 0;

}

/// Функция очистки массива

void EmptyArray(void *) {

/// Входим в критическую секцию

EnterCriticalSection(&critsect);

cout << "THREAD #" << (iCount+1) << " -> EmptyArray\n";

for (int i = 0; i < MAX_ARRAY; i++)

Array[i] = 0;

iCount += 1; /// Увеличиваем счетчик

/// Выходим из критической секции

LeaveCriticalSection(&critsect);

_endthread(); /// Завершает поток

}

/// Функция вывода массива

void PrintArray(void *) {

/// Входим в критическую секцию

EnterCriticalSection(&critsect);

cout << "THREAD #" << (iCount+1) << " -> PrintArray\n";

for (int i = 0; i < MAX_ARRAY; i++)

cout << Array[i] << " ";

cout << '\n';

iCount += 1; /// Увеличиваем счетчик

/// Выходим из критической секции

LeaveCriticalSection(&critsect);

_endthread(); /// Завершает поток

}

/// Функция заполнения массива числами

void FullArray(void *) {

/// Входим в критическую секцию

EnterCriticalSection(&critsect);

cout << "THREAD #" << (iCount+1) << " -> FullArray\n";

for (int i = 0; i < MAX_ARRAY; i++)

Array[i] = i;

iCount += 1; /// Увеличиваем счетчик

/// Выходим из критической секции

LeaveCriticalSection(&critsect);

_endthread(); /// Завершает поток

}

Спинлок (англ. Spinlock — циклическая блокировка) — низкоуровневый примитив синхронизации, применяемый в многопроцессорных системах для реализации взаимного исключения (мьютекса).

Физически спинлок представляет собой переменную в памяти. Если спин-блокировка была захвачена (в переменную было записано определенное значение), последующая попытка захватить спин-блокировку любым потоком приведет к бесконечному циклу с попыткой захвата спин-блокировки (состояние потока busy-waiting). Цикл закончится только тогда, когда прежний владелец спин-блокировки освободит ее (запишет другое значение в переменную).