Чтение онлайн

ЖАНРЫ

Программирование на Visual C++. Архив рассылки

Jenter Алекс

Шрифт:
Способы обнаружения ошибок

Сначала стоит обратить внимание на "официальный" способ обнаружения блокировок. Если бы кроме ::EnterCriticalSection и ::TryEnterCtiticalSection существовал бы еще и ::EnterCriticalSectionWithTimeout, то достаточно было бы просто указать какое-нибудь резонное значение для интервала ожидания, например, 30 секунд. Если критическая секция не освободилась в течение указанного времени, то с очень большой вероятностью она не освободится никогда. Имеет смысл подключить отладчик и посмотреть, что же творится в соседних нитьх. Но увы. Никаких ::EnterCriticalSectionWithTimeout в Win32 не предусмотрено. Вместо этого есть поле CriticalSectionDefaultTimeout

в структуре IMAGE_LOAD_CONFIG_DIRECTORY32, которое всегда равно нулю и, судя по всему, не используется. Зато используется ключ в реестре "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\CriticalSectionTimeout", который по умолчанию равен 30 суткам, и по истечению этого времени в системный лог попадает строка "RTL: Enter Critical Section Timeout (2 minutes)\nRTL: Pid.Tid XXXX.YYYY, owner tid ZZZZ\nRTL: Re-Waiting\n". К тому же это верно только для систем WindowsNT/2k/XP и только с CheckedBuild. У вас установлен CheckedBuild? Нет? А зря. Вы теряете исключительную возможность увидеть эту замечательную строку.

Ну, а какие у нас альтернативы? Да, пожалуй, только одна. Не использовать 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);

Поделиться с друзьями: