Процессы в Windows - Синхронизация потоков

ОГЛАВЛЕНИЕ

Синхронизация потоков

Работая параллельно, потоки совместно используют адресное пространство процесса. Также все они имеют доступ к описателям (handles) открытых в процессе объектов. А что делать, если несколько потоков одновременно обращаются к одному ресурсу или необходимо как-то упорядочить работу потоков? Для этого используют объекты синхронизации и соответствующие механизмы.

Мьютексы

Мьютексы (Mutex) это объекты ядра, которые создаются функцией CreateMutex(). Мьютекс бывает в двух состояниях - занятом и свободном. Мьютексом хорошо защищать единичный ресурс от одновременного обращения к нему разными потоками.

Пример 3. Допустим, в программе используется ресурс, например, файл или буфер в памяти. Функция WriteToBuffer() вызывается из разных потоков. Чтобы избежать коллизий при одновременном обращении к буферу из разных потоков, используем мьютекс. Прежде чем обратиться к буферу, ожидаем <освобождения> мютекса.


HANDLE hMutex;

int main()
{
hMutex = CreateMutex( NULL, FALSE, NULL); // Создаем мьютекс в свободном состоянии
...
// Создание потоков, и т.д.
...
}
BOOL WriteToBuffer()
{
DWORD dwWaitResult;
// Ждем освобождения мьютекса перед тем как обратиться к буферу.
dwWaitResult = WaitForSingleObject( hMutex, 5000L); // 5 секунд на таймаут
 
if (dwWaitResult == WAIT_TIMEOUT) // Таймаут. Мьютекс за это время не освободился.
{
return FALSE;
}
else // Мьютекс освободился, и наш поток его занял. Можно работать.
{
Write_to_the_buffer().
...
ReleaseMutex(hMutex); // Освобождаем мьютекс.
}
return TRUE;
}

Семафоры

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

Пример 4. Классический пример использования семафора это очередь элементов, которую обрабатывают несколько потоков. Потоки "разбирают" элементы из очереди. Если очередь пуста, потоки должны "спать", ожидая появления новых элементов. Для учета элементов в очереди используется семафор.


class CMyQueue
{
HANDLE m_hSemaphore; // Семафор для учета элементов очереди
// Описание других объектов для хранения элементов очереди

public:
CMyQueue()
{
m_hSemaphore = CreateSemaphore(NULL, 0, 1000, NULL); //начальное значение счетчика = 0

//максимальное значение = 1000

// Инициализация других объектов
}
~CMyQueue()
{
CloseHandle( m_hSemaphore);
// Удаление других объектов
}
void AddItem(void * NewItem)
{
// Добавляем элемент в очередь
// Увеличиваем счетчик семафора на 1.
ReleaseSemaphore(m_hSemaphore,1, NULL);
}
void GetItem(void * Item)
{
// Если очередь пуста, то потоки, вызвавшие этот метод,
// будут находиться в ожидании...

WaitForSingleObject(m_hSemaphore,INFINITE);
 
// Удаляем элемент из очереди
}
};

Замечание. В этом примере мы считаем, что сами процедуры добавления элемента в очередь и удаления из очереди безопасны с точки зрения многопоточности. Не будем пока касаться деталей их реализации. Подробнее мы рассмотрим это в примере 9.