И, наконец, поле SpinCount. Это поле используется только многопроцессорными системами. В однопроцессорных системах, если критическая секция занята другой нитью, можно только переключить управление на нее и подождать наступления нашего события. В многопроцессорных системах есть альтернатива: прогнать некоторое количество раз холостой цикл, проверяя каждый раз, не освободилась ли наша критическая секция. Если за SpinCount раз это не получилось, переходим к ожиданию. Это гораздо эффективнее, чем переключение на планировщик ядра и обратно. Кроме того, в WindowsNT/2k старший бит этого поля служит для индикации того, что объект ядра, хендл которого находится в поле LockSemaphore, должен быть создан заранее. Если системных ресурсов для этого недостаточно, система сгенерирует исключение, и программа может "урезать" свою функциональнось. Или совсем завершить работу.
API для работы с критическими секциями
BOOL InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection );
BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount );
Заполняют поля структуры, адресуемой lpCriticalSection.
После вызова любой из этих функций критическая секция готова к работе.
Листинг 1. Псевдокод RtlInitializeCriticalSection из ntdll.dll
VOID RtlInitializeCriticalSection(LPRTL_CRITICAL_SECTION pcs) {
RtlInitializeCriticalSectionAndSpinCount(pcs, 0);
}
VOID RtlInitializeCriticalSectionAndSpinCount(LPRTL_CRITICAL_SECTION pcs, DWORD dwSpinCount) {
pcs->DebugInfo = NULL;
pcs->LockCount = -1;
pcs->RecursionCount = 0;
pcs->OwningThread = 0;
pcs->LockSemaphore = NULL;
pcs->SpinCount = dwSpinCount;
if (0x80000000 & dwSpinCount) _CriticalSectionGetEvent(pcs);
}
DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION lpCriticalSection , DWORD dwSpinCount);
Устанавливает значение поля SpinCount и возвращает его предыдущее значение. Напоминаю, что старший бит отвечает за "привязку" события, используемого для ожидания доступа к данной критической секции.
Листинг 2. Псевдокод RtlSetCriticalSectionSpinCount из ntdll.dll
DWORD RtlSetCriticalSectionSpinCount(LPRTL_CRITICAL_SECTION pcs, DWORD dwSpinCount) {
DWORD dwRet = pcs->SpinCount;
pcs->SpinCount = dwSpinCount;
return dwRet;
}
VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection );
Освобождает ресурсы, занимаемые критической секцией.
Листинг 3. Псевдокод RtlDeleteCriticalSection из ntdll.dll
VOID RtlDeleteCriticalSection(LPRTL_CRITICAL_SECTION pcs) {
pcs->DebugInfo = NULL;
pcs->LockCount = -1;
pcs->RecursionCount = 0;
pcs->OwningThread = 0;
if (pcs->LockSemaphore) {
::CloseHandle(pcs->LockSemaphore);
pcs->LockSemaphore = NULL;
}
}
VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection );
BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection );
Осуществляют "захват" критической секции. Если критическая секция занята другой нитью, то ::EnterCriticalSection() будет ждать, пока та освободится, а ::TryEnterCriticalSection() вернет FALSE.
Листинг 4. Псевдокод RtlEnterCriticalSection из ntdll.dll
VOID RtlEnterCriticalSection(LPRTL_CRITICAL_SECTION pcs) {
if (::InterlockedIncrement(&pcs->LockCount)) {
if (pcs->OwningThread == (HANDLE)::GetCurrentThreadId()) {
pcs->RecursionCount++;
return;
}
RtlpWaitForCriticalSection(pcs);
}
pcs->OwningThread = (HANDLE)::GetCurrentThreadId();
pcs->RecursionCount = 1;
}
BOOL RtlTryEnterCriticalSection(LPRTL_CRITICAL_SECTION pcs) {
if (-1L == ::InterlockedCompareExchange(&pcs->LockCount, 0, -1)) {
pcs->OwningThread = (HANDLE)::GetCurrentThreadId();
pcs->RecursionCount = 1;
} else if (pcs->OwningThread == (HANDLE)::GetCurrentThreadId()) {
::InterlockedIncrement(&pcs->LockCount);
pcs->RecursionCount++;
} else return FALSE;
return TRUE;
}
VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection );
Освобождает критическую секцию
Листинг 5. Псевдокод RtlLeaveCriticalSection из ntdll.dll
VOID RtlLeaveCriticalSectionDbg(LPRTL_CRITICAL_SECTION pcs) {
if (--pcs->RecursionCount) ::InterlockedDecrement(&pcs->LockCount);
else if (::InterlockedDecrement(&pcs->LockCount) >= 0) RtlpUnWaitCriticalSection(pcs);
}
Классы-обертки для критических секций
Листинг 6. Код классов CLock, CAutoLock и CScopeLock
class CLock {
friend class CScopeLock;
CRITICAL_SECTION m_CS;
public:
void Init() { ::InitializeCriticalSection(&m_CS); }
void Term() { ::DeleteCriticalSection(&m_CS); }
void Lock() { ::EnterCriticalSection(&m_CS); }
BOOL TryLock() { return ::TryEnterCriticalSection(&m_CS); }
void Unlock() { ::LeaveCriticalSection(&m_CS); }
};
class CAutoLock : public CLock {
public:
CAutoLock() { Init(); }
~CAutoLock() { Term(); }
};
class CScopeLock {
LPCRITICAL_SECTION m_pCS;
public:
CScopeLock(LPCRITICAL_SECTION pCS) : m_pCS(pCS) { Lock(); }
CScopeLock(CLock& lock) : m_pCS(&lock.m_CS) { Lock(); }
Читать дальше