Критическая секция

Критическая секция (critical section) - это участок кода, требующий монопольного доступа к каким-то общим данным (ресурсам); параллельное выполнение этого участка кода несколькими потоками может привести к непредсказуемым или неверным результатам.

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

Название критическая секция используется и для объекта синхронизации – структуры (типа данных) Windows, используемой для организации критической секции в изложенном выше смысле.

Для реализации взаимного исключения с помощью критической секции необходимо глобально (обеспечить доступность из всех потоков процесса, конкурирующих за ресурс) объявить переменную типа CRITICAL_SECTION. Структура CRITICAL_SECTION представляет для программиста «черный ящик», то есть не происходит прямого обращения к полям данной структуры. Работа с CRITICAL_SECTION осуществляется исключительно через специальные функции, которым передается адрес соответствующего экземпляра данной структуры.

Перед началом использования (до обращения какого-либо потока к защищенному ресурсу) элементы структуры необходимо инициализировать с помощью вызова:

void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

Захват критической секции производится с помощью функций:

void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

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

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

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

Если значения элементов структуры указывают на то, что ресурс занят другим потоком, EnterCriticalSection переводит вызывающий поток в режим ожидания. Система запоминает, что данный поток хочет получить доступ к ресурсу, и - как только поток, занимавший критическую секцию, освобождает ее, ожидающий поток переходит в активное состояние и занимает критическую секцию. Интересным фактом является то, что потоки, ожидающие освобождения критической секции, на самом деле не блокируются «навечно». Данная функция реализована таким образом, что по истечении определенного времени, генерирует исключение (ожидание прекращается ошибкой). Длительность времени ожидания функцией EnterCriticalSection определяется значением системного реестра Windows. Длительность времени ожидания измеряется в секундах и по умолчанию равна 2 592 000 секунд (что составляет ровно 30 суток).

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

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

вызов:

void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

Эта функция просматривает элементы структуры CRITICAL_SECTION и уменьшает счетчик числа захватов ресурса вызывающим потоком на 1. Если его значение больше 0, LeaveCriticalSection ничего не делает и просто возвращает управление. Если значение счетчика достигло 0, LeaveCriticalSection сначала выясняет, есть ли в системе другие потоки, ждущие данный ресурс в вызове EnterCriticalSection. Если есть хотя бы один такой поток, функция настраивает значения элементов структуры, чтобы они сигнализировали о занятости ресурса, и отдает его одному из ожидающих потоков при присутствии таковых. LeaveCriticalSection никогда не приостанавливает поток, а управление возвращает немедленно.

При завершении работы с критической секцией для нее необходимо вызвать функцию

void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

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

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

Однако критическая секция имеет один существенный недостаток: она не может быть использована для синхронизации потоков принадлежащих различным процессам.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  




Подборка статей по вашей теме: