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(). Это первая (а часто и единственная проверка) на пути к "захвату" критической секции. Если после увеличения в этом поле находится ноль, это означает, что до этого момента непарных вызовов ::EnterCriticalSection() из других потоков не было. В этом случае можно забрать данные, охраняемые этой критической секцией в монопольное пользование. Таким образом, если критическая секция интенсивно используется не более чем одним потоком, ::EnterCriticalSection() практически вырождается в ++LockCount, а ::LeaveCriticalSection() в --LockCount. Это очень важно. Это означает, что использование многих тысяч критических секций в одном процессе не повлечет значительного расхода ни системных ресурсов, ни процессорного времени.
В поле RecursionCount хранится количество повторных вызовов ::EnterCriticalSection() из одного и того же потока. Действительно, если вызвать ::EnterCriticalSection() из одного и того же потока несколько раз, все вызовы будут успешны. Т.е. вот такой код не остановится навечно во втором вызове ::EnterCriticalSection(), а отработает до конца.
Действительно, критические секции предназначены для защиты данных от доступа из нескольких потоков. Многократное использование одной и той же критической секции из одного потока не приведет к ошибке. Это вполне нормальное явление. Следите, чтобы количество вызовов ::EnterCriticalSection() и ::LeaveCriticalSection() совпадало, и все будет хорошо.
Поле OwningThread содержит 0 для никем не занятых критических секций или уникальный идентификатор потока-владельца. Это поле проверяется, если при вызове ::EnterCriticalSection() поле LockCount после увеличения на единицу оказалось больше нуля. Если OwningThread совпадает с уникальным идентификатором текущего потока, то RecursionCount просто увеличивается на единицу и ::EnterCriticalSection() возвращается немедленно. Иначе ::EnterCriticalSection() будет дожидаться, пока поток, владеющий критической секцией, не вызовет ::LeaveCriticalSection() необходимое количество раз.
Поле LockSemaphore используется, если нужно подождать, пока критическая секция освободится. Если LockCount больше нуля, и OwningThread не совпадает с уникальным идентификатором текущего потока, то ждущий поток создает объект ядра (событие) и вызывает ::WaitForSingleObject(LockSemaphore). Поток-владелец, после уменьшения RecursionCount, проверяет его, и если значение этого поля равно нулю, а LockCount больше нуля, то это значит, что есть как минимум однин поток, ожидающий, пока LockSemaphore не окажется в состоянии "случилось!". Для этого поток-владелец вызывает ::SetEvent(), и какой-то один (только один) из ожидающих потоков пробуждается и получает доступ к критическим данным.
И, наконец, поле SpinCount. Это поле используется только многопроцессорными системами. В однопроцессорных системах, если критическая секция занята другим потоком, можно только переключить управление на нее и подождать наступления события. В многопроцессорных системах есть альтернатива: прогнать некоторое количество раз холостой цикл, проверяя каждый раз, не освободилась ли критическая секция. Если за SpinCount раз это не получилось, переходим к ожиданию. Это гораздо эффективнее, чем переключение на планировщик ядра и обратно.[2]
Вывод
Использование механизма критических секций часто бывает необходимо при работе с несколькими потоками. Критические интервалы позволяют избегать ситуации, когда два потока одновременно используют критические ресурсы, а значит гарантирует корректность результатов работы потоков. Важно помнить, что критические секции не являются объектами ядра операционной системы, а являются локальными для каждого из процессов, и что для одного и того же потока вход в критическую секцию возможен неоднократно. Рекомендуется не пытаться экономить ресурсы системы на критических секциях. Много сэкономить все равно не получится, а корректная работа потоков при этом гарантироваться не будет.