ГлавнаяarrowСистемное программированиеarrow24. Синхронизация потоков в Windows

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

Для программ, использующих несколько потоков или процессов, необходимо, чтобы все они выполняли возложенные на них функции в нужной последовательности. В среде Windows 9x для этой цели предлагается использовать несколько механизмов, обеспечивающих слаженную работу потоков. Эти механизмы называют механизмами синхронизации.
Предположим, разрабатывается программа, в которой параллельно работают два потока, каждый из которых обращается к одной глобальной переменной. Один поток при каждом обращении к этой переменной выполняет её инкремент, а второй - декремент. При одновременной работе потоков неизбежно возникает такая ситуация: один поток прочитал значение глобальной переменной в локальную; ОС прерывает его, так как закончился выделенный ему квант времени процессора, и передаёт управление другому потоку; второй поток также считал значение глобальной переменной в локальную, декрементировал её и записал новое значение обратно; ОС вновь передаёт управление первому потоку, тот, ничего не зная о действиях второго потока, инкрементирует свою локальную переменную и записывает её значение в глобальную. Очевидно, что изменения, внесённые вторым потоком, утеряны.
Для исключения подобных ситуаций, необходимо разделить во времени использование совместных данных. В таких случаях используются механизмы синхронизации, которые обеспечивают корректную работу нескольких потоков.
ОС предлагает несколько синхронизирующих объектов: критический раздел (critical section), мьютекс (mutex), семафор (semaphore) и событие (event). Все они, за исключением критических разделов, являются объектами ядра.
Критические разделы. Критический раздел (critical section) - объект, к которому должен обратиться поток перед получением эксклюзивного доступа к каким-либо общим данным. Среди синхронизирующих объектов критические разделы наиболее просты, но применимы для синхронизации потоков лишь принадлежащих одному процессу. Они дают возможность сделать так, чтобы единовременно только один поток получал доступ к определенному региону данных. Критический раздел анализирует значение специальной перемен­ной процесса, которая используется как флаг, предотвращающий исполнение некоторого участка кода несколькими потоками одновременно.
Мьютексы. Объекты мьютекс очень похожи на критические разделы за исключением того, что с их помощью можно синхронизировать доступ к общим данным со стороны нескольких процессов, точнее со стороны потоков разных процессов. Кроме того, мьютексы являются объектами ядра. Если поток является владельцем мьютекса, он обладает правом эксклюзивного использования ресурса, который защищается этим мьютексом. Ни один другой поток не может завладеть мьютексом, который уже принадлежит одному из по­токов. Если мьютекс защищает какие-то совместно используемые данные, он сможет выполнить свою функцию только в случае, если перед обращением к этим данным каждый из потоков будет проверять состояние этого мьютекса. Windows расценивает мьютекс как объект общего доступа, который можно пере­вести в сигнальное состояние или сбросить. Сигнальное состояние мьютекса говорит о том, что он занят. Потоки должны самостоятельно ана­лизировать текущее состояние мьютексов. Ели требуется чтобы к мьютексу могли обратиться потоки других процессов, ему надо присвоить имя.
События. События обычно просто оповещают об окончании какой-либо операции, они также являются объектами ядра. Со­бытия могут быть мануальными (manual) и единичными (single).
Единичное событие (single event) - это скорее общий флаг. Событие находится в сигнальном состоянии, если его установил какой-нибудь поток. Если для работы программы требуется, чтобы в случае возникновения события на него реа­гировал только один из потоков, в то время как все остальные потоки продолжа­ли ждать, то используют единичное событие. После установления события только один ожидающий поток будет оповещен об этом событии и, соответственно, сможет продолжить работу. После этого система автоматически сбросит событие. Если в момент установки события не существует ни одного ожидающего потока, событие останется в сигнальном положении до тех пор, пока в системе не появится какой-либо ожидающий это событие поток.
Мануальное событие (manual event) - это не про­сто общий флаг для нескольких потоков. Оно выполняет несколько более слож­ные функции. Любой поток может установить это событие или сбросить (очистить) его. Если собы­тие установлено, оно останется в этом состоянии сколь угодно долгое время, вне зависимости от того, сколько потоков ожидают установки этого события. Когда все потоки, ожидающие этого события, по­лучат сообщение о том, что событие произошло, оно автомати­чески сбросится.
Если требуется, чтобы к событию смогли обратиться потоки других процессов, нужно присвоить этому событию уникальное имя.
Если событие больше не нужно, программа должна обратиться к вызову CloseHandle() для того, чтобы уменьшить его счётчик пользователей. Если счетчик будет равен нулю, объект событие удалится системой.
Семафоры. Объекты ядра “семафор” используются для учёта ресурсов и служат для ограничения одновременного доступа к ресурсу нескольких потоков. Используя семафор, можно организовать работу программы таким образом, что к ресурсу одновременно смо­гут получить доступ несколько потоков, однако количество этих потоков будет ограничено. Создавая семафор, указываеся максимальное количество пото­ков, которые одновременно смогут работать с ресурсом. Если в некоторый момент времени с ресурсом работает меньшее количество потоков, семафор ос­тается в сигнальном состоянии. Как только количество потоков, желающих ра­ботать с ресурсом, становится больше допустимого, семафор блокирует доступ к ресурсу. Каждый раз, когда программа обращается к семафору, значение счетчика ресурсов семафора уменьша­ется на единицу. Когда значение счетчика ресурсов становится равным нулю, семафор недоступен.
2 Системные вызовы для работы с критическими разделами
Чтобы создать критический раздел, нужно определить глобальную переменную типа CRITICAL_SECTION и передать её адрес функции InitializeCriticalSection(). К этой функции следует обращаться из глав­ного потока программы и до обращения к критическому разделу из других потоков. В начале критической секции кода потока, т.е. перед использованием совместных данных, следует расположить обращение к функции EnterCriticalSection(). Если в этот момент критический раздел используется каким-либо другим потоком, ис­полнение потока, обратившегося к EnterCriticalSection(), будет блокировано. Блокирование продолжается до тех пор, пока поток, использующий критический раздел, не обратится к функции LeaveCriticalSection().Таким образом, функция EnterCriticalSection() выполняет захват критического раздела, а функция LeaveCriticalSection() - его высвобождение. При завершении программы все переменные типа CRITICAL_SECTION нужно удалить функцией DeleteCriticalSection(). Она высвобождает все ресурсы, включенные в критический раздел. Объекты ядра мьютекс, событие и семафор - служат одной цели: синхронизация потоков. Заставляют поток ждать освобождения какого-либо объекта ядра две основные функции. Функция WaitForSingleObject () Для объектов мьютекс, семафор и единичное событие эта функция меняет их состояние на занятое. Как только один из таких объектов становится свободным, поток вызвавший функцию пробуждается и меняет статус объекта на занятый. Функция WaitForMultipleObjects () аналогична предыдущей за тем исключением, что позволяет ждать освобождения сразу нескольких объектов (не более 64).
5 Системные вызовы для работы с семафорами
Создания нового семафора осуществляется вызовом CreateSemaphore(). При создании надо указать начальное состояние счетчика. Функция возвращает возвращает дескриптор созданного семафора и увеличивает счетчик числа пользователей объекта на 1. Следовательно, при завершении работы с семафором, надо вызвать функцию CloseHandle(). Если требуется открыть уже существующий семафор, то следует воспользоваться функцией OpenSemaphore().
6 Системные вызовы для работы с событиями
Вызов CreateEvent() создает событие с указанным именем в слу­чае, если такого события еще не создано в системе. Если событие с указанным именем уже существует, вызов CreateEvent() просто открывает существующее со­бытие.
 

Hosted by uCoz