Часть полного текста документа:Критические секции Павел Блудов Введение Критические секции -- это объекты, используемые для блокировки доступа всех нитей (threads) приложения, кроме одной, к некоторым важным данным в один момент времени. Например, имеется переменная m_pObject и несколько нитей, вызывающих методы объекта, на который ссылается m_pObject, причем эта переменная может изменять свое значение время от времени. Иногда там даже оказывается нуль. Предположим, имеется вот такой код: // Нить №1 void Proc1() { if (m_pObject) m_pObject->SomeMethod(); } // Нить №2 void Proc2(IObject *pNewObject) { if (m_pObject) delete m_pObject; m_pObject = pNewobject; } Тут мы имеем потенциальную опасность вызова m_pObject->SomeMethod() после того, как объект был уничтожен при помощи delete m_pObject. Дело в том, что в системах с вытесняющей многозадачностью выполнение любой нити процесса может прерваться в самый неподходящий для нее момент времени, и начнет выполняться совершенно другая нить. В данном примере неподходящим моментом будет тот, в котором нить №1 уже проверила m_pObject, но еще не успела вызвать SomeMethod(). Выполнение нити №1 прервалось, и начала исполняться нить №2. Причем нить №2 успела вызвать деструктор объекта. Что же произойдет, когда нить №1 получит немного процессорного времени и вызовет-таки SomeMethod() у уже несуществующего объекта? Наверняка что-то ужасное. Именно тут приходят на помощь критические секции. Перепишем наш пример. // Нить №1 void Proc1() { ::EnterCriticalSection(&m_lockObject); if (m_pObject) m_pObject->SomeMethod(); ::LeaveCriticalSection(&m_lockObject); } // Нить №2 void Proc2(IObject *pNewObject) { ::EnterCriticalSection(&m_lockObject); if (m_pObject) delete m_pObject; m_pObject = pNewobject; ::LeaveCriticalSection(&m_lockObject); } Код, помещенный между ::EnterCriticalSection() и ::LeaveCriticalSection() с одной и той же критической секцией в качестве параметра, никогда не будет выполняться параллельно. Это означает, что если нить №1 успела "захватить" критическую секцию m_lockObject, то при попытке нити №2 заполучить эту же критическую секцию в свое единоличное пользование, ее выполнение будет приостановлено до тех пор, пока нить №1 не "отпустит" m_lockObject при помощи вызова ::LeaveCriticalSection(). И наоборот, если нить №2 успела раньше нити №1, то та "подождет", прежде чем начнет работу с m_pObject. Работа с критическими секциями Что же происходит внутри критических секций и как они устроены? Прежде всего, следует отметить, что критические секции - это не объекты ядра операционной системы. Практически вся работа с критическими секциями происходит в создавшем их процессе. Из этого следует, что критические секции могут быть использованы только для синхронизации в пределах одного процесса. Теперь рассмотрим критические секции поближе. Структура RTL_CRITICAL_SECTION typedef struct _RTL_CRITICAL_SECTION { PRTL_CRITICAL_SECTION_DEBUG DebugInfo; // Используется операционной системой LONG LockCount; // Счетчик использования этой критической секции LONG RecursionCount; // Счетчик повторного захвата из нити-владельца HANDLE OwningThread; // Уникальный ID нити-владельца HANDLE LockSemaphore; // Объект ядра используемый для ожидания ULONG_PTR SpinCount; // Количество холостых циклов перед вызовом ядра } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION; Поле LockCount увеличивается на единицу при каждом вызове ::EnterCriticalSection() и уменьшается при каждом вызове ::LeaveCriticalSection(). ............ |