Листинг 15. Класс CLock с новым методом
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); }
BOOL Check() { return CheckCriticalSection(&m_CS); }
};
Использовать метод Check() в release-конфигурациях не стоит, возможно, что в будущем, в какой-нибудь Windows64, структура RTL_CRITICAL_SECTION изменится и результат такой проверки не определен. Так что ему самое место "жить" внутри всяческих ASSERT'ов.
Итак, что мы имеем? Мы имеем проверку на лишний вызов ::LeaveCriticalSection() и ту же трассировку для блокировок. Не так уж много. Особенно, если трассировка о блокировке имеет место, а вот нить, забывшая освободить критическую секцию, давно завершилась. Как быть? Вернее, что бы еще придумать, чтобы ошибку проще было выявить? Как минимум, прикрутить сюда __LINE__ и __FILE__, константы, соответствующие текущей строке и имени файла на момент компиляции этого метода.
VOID EnterCriticalSectionDbg(LPCRITICAL_SECTION pcs, int nLine = __LINE__, azFile = __FILE__);
Компилируем, запускаем… Результат удивительный. Хотя правильный. Компилятор честно подставил номер строки и имя файла, соответствующие началу нашей EnterCriticalSectionDbg(). Так что придется попотеть немного больше. __LINE__ и __FILE__ нужно вставить в #define'ы, тогда мы получим действительные номер строки и имя исходного файла. Теперь вопрос, куда же сохранить эти параметры для дальнейшего использования? Причем хочется оставить за собой возможность вызова стандартных функций API наряду с нашими собственными? На помощь приходит C++: просто создадим свою структуру, унаследовав ее от RTL_CRITICAL_SECTION. Итак:
Листинг 16. Реализация критических секций с сохранением строки и имени файла
#if defined(_DEBUG) && !defined(_NO_DEADLOCK_TRACE)
#define DEADLOCK_TIMEOUT 30000
#define CS_DEBUG 2
// Наша структура взамен CRITICAL_SECTION
struct CRITICAL_SECTION_DBG : public CRITICAL_SECTION {
// Добавочные поля
int m_nLine;
LPCSTR m_azFile;
};
typedef struct CRITICAL_SECTION_DBG *LPCRITICAL_SECTION_DBG;
// Создаем на лету событие для операций ожидания,
// но никогда его не освобождаем. Так удобней для отладки
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_DBG pcs, int nLine, LPCSTR azFile) {
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"
"Owner lock from %hs line %u, waiter %hs line %u\n",
DEADLOCK_TIMEOUT, ::GetCurrentThreadId(), pcs->OwningThread,
pcs->m_azFile, pcs->m_nLine, azFile, nLine);
}
} 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 InitializeCriticalSectionDbg(LPCRITICAL_SECTION_DBG pcs) {
// Пусть система заполнит свои поля
InitializeCriticalSection(pcs);
// Заполняем наши поля
pcs->m_nLine = 0;
pcs->m_azFile = NULL;
}
// Освобождаем ресурсы, занимаемые критической секцией
inline VOID DeleteCriticalSectionDbg(LPCRITICAL_SECTION_DBG pcs) {
// Проверяем, чтобы не было удалений "захваченных" критических секций
ATLASSERT(0 == pcs->m_nLine && NULL == pcs->m_azFile);
// Остальное доделает система
DeleteCriticalSection(pcs);
}
// Заполучем критическую секцию в свое пользование
inline VOID EnterCriticalSectionDbg(LPCRITICAL_SECTION_DBG pcs, int nLine, LPSTR azFile) {
if (::InterlockedIncrement(&pcs->LockCount)) {
// LockCount стал больше нуля.
// Проверяем идентификатор нити
if (pcs->OwningThread == (HANDLE)::GetCurrentThreadId()) {
// Нить та же самая. Критическая секция наша.
// Никаких дополнительных действий не производим.
// Это не совсем верно, так как возможно, что непарный
// вызов ::LeaveCriticalSection() был на n-ном заходе,
// и это прийдется отлавливать вручную, но реализация
Читать дальше