Ну, а какие у нас альтернативы? Да, пожалуй, только одна. Не использоватьAPI для работы с критическими секциями. Вместо них написать свои собственные. Пусть даже не такие обточенные напильником, как в WindowsNT. Не страшно. Нам это понадобится только в debug-конфигурациях. В release'ах мы будем продолжать использовать оригинальный API от Майкрософт. Для этого напишем несколько функций полностью совместимых по типам и количеству аргументов с "настоящим" API и добавим #defineкак у MFC для переопределения оператора newв debug-конфигурациях.
Листинг 14. Собственная реализация критических секций
#if defined(_DEBUG) && !defined(_NO_DEADLOCK_TRACE)
#define DEADLOCK_TIMEOUT 30000
#define CS_DEBUG 1
// Создаем на лету событие для операций ожидания,
// но никогда его не освобождаем. Так удобней для отладки
static inline HANDLE _CriticalSectionGetEvent(LPCRITICAL_SECTION pcs) {
HANDLE ret = pcs->LockSemaphore;
if (!ret) {
HANDLE sem = ::CreateEvent(NULL, false, false, NULL);
ATLASSERT(sem);
if (!(ret = (HANDLE)::InterlockedCompareExchangePointer(
&pcs->LockSemaphore, sem, NULL))) ret = sem;
else ::CloseHandle(sem); // Кто-то успел раньше
}
return ret;
}
// Ждем, пока критическая секция не освободится либо время ожидания
// будет превышено
static inline VOID _WaitForCriticalSectionDbg(LPCRITICAL_SECTION pcs) {
HANDLE sem = _CriticalSectionGetEvent(pcs);
DWORD dwWait;
do {
dwWait = ::WaitForSingleObject(sem, DEADLOCK_TIMEOUT);
if (WAIT_TIMEOUT == dwWait) {
ATLTRACE("Critical section timeout (%u msec):"
" tid 0x%04X owner tid 0x%04X\n", DEADLOCK_TIMEOUT,
::GetCurrentThreadId(), pcs->OwningThread);
}
} while(WAIT_TIMEOUT == dwWait);
ATLASSERT(WAIT_OBJECT_0 == dwWait);
}
// Выставляем событие в активное состояние
static inline VOID _UnWaitCriticalSectionDbg(LPCRITICAL_SECTION pcs) {
HANDLE sem = _CriticalSectionGetEvent(pcs);
BOOL b = ::SetEvent(sem);
ATLASSERT(b);
}
// Заполучем критическую секцию в свое пользование
inline VOID EnterCriticalSectionDbg(LPCRITICAL_SECTION pcs) {
if (::InterlockedIncrement(&pcs->LockCount)) {
// LockCount стал больше нуля.
// Проверяем идентификатор нити
if (pcs->OwningThread == (HANDLE)::GetCurrentThreadId()) {
// Нить та же самая. Критическая секция наша.
pcs->RecursionCount++;
return;
}
// Критическая секция занята другой нитью.
// Придется подождать
_WaitForCriticalSectionDbg(pcs);
}
// Либо критическая секция была "свободна",
// либо мы дождались. Сохраняем идентификатор текущей нити.
pcs->OwningThread = (HANDLE)::GetCurrentThreadId();
pcs->RecursionCount = 1;
}
// Заполучаем критическую секцию если она никем не занята
inline BOOL TryEnterCriticalSectionDbg(LPCRITICAL_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;
}
// Освобождаем критическую секцию
inline VOID LeaveCriticalSectionDbg(LPCRITICAL_SECTION pcs) {
// Проверяем, чтобы идентификатор текущей нити совпадал
// с идентификатор нити-влядельца.
// Если это не так, скорее всего мы имеем дело с ошибкой
ATLASSERT(pcs->OwningThread == (HANDLE)::GetCurrentThreadId());
if (--pcs->RecursionCount) {
// Не последний вызов из этой нити.
// Уменьшаем значение поля LockCount
::InterlockedDecrement(&pcs->LockCount);
} else {
// Последний вызов. Нужно "разбудить" какую-либо
// из ожидающих ниток, если таковые имеются
ATLASSERT(NULL != pcs->OwningThread);
pcs->OwningThread = NULL;
if (::InterlockedDecrement(&pcs->LockCount) >= 0) {
// Имеется, как минимум, одна ожидающая нить
_UnWaitCriticalSectionDbg(pcs);
}
}
}
// Удостоверяемся, что ::EnterCriticalSection() была вызвана
// до вызова этого метода
inline BOOL CheckCriticalSection(LPCRITICAL_SECTION pcs) {
return pcs->LockCount >= 0
&& pcs->OwningThread == (HANDLE)::GetCurrentThreadId();
}
// Переопределяем все функции для работы с критическими секциями.
// Определение класса CLock должно быть после этих строк
#define EnterCriticalSection EnterCriticalSectionDbg
#define TryEnterCriticalSection TryEnterCriticalSectionDbg
#define LeaveCriticalSection LeaveCriticalSectionDbg
#endif
Ну и заодно добавим еще один метод в наш класс CLock
Читать дальше