Рейтинговые книги
Читем онлайн Программирование на Visual C++. Архив рассылки - Алекс Jenter

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 139 140 141 142 143 144 145 146 147 ... 156

 }

 // Либо критическая секция была "свободна",

 // либо мы дождались. Сохраняем идентификатор текущей нити.

 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

Листинг 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%04Xn"

    "Owner lock from %hs line %u, waiter %hs line %un",

    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-ном заходе,

   // и это прийдется отлавливать вручную, но реализация

   // стека для __LINE__ и __FILE__ сделает нашу систему

   // более громоздкой. Если это действительно необходимо,

   // Вы всегда можете сделать это самостоятельно

   pcs->RecursionCount++;

   return;

  }

  // Критическая секция занята другой нитью.

  // Придется подождать

  _WaitForCriticalSectionDbg(pcs, nLine, azFile);

 }

 // Либо критическая секция была "свободна",

 // либо мы дождались. Сохраняем идентификатор текущей нити.

 pcs->OwningThread = (HANDLE)::GetCurrentThreadId();

 pcs->RecursionCount = 1;

 pcs->m_nLine = nLine;

 pcs->m_azFile = azFile;

}

// Заполучаем критическую секцию если она никем не занята

inline BOOL TryEnterCriticalSectionDbg(LPCRITICAL_SECTION_DBG pcs, int nLine, LPSTR azFile) {

 if (-1L == ::InterlockedCompareExchange(&pcs->LockCount, 0, -1)) {

  // Это первое обращение к критической секции

  pcs->OwningThread = (HANDLE)::GetCurrentThreadId();

  pcs->RecursionCount = 1;

  pcs->m_nLine = nLine;

  pcs->m_azFile = azFile;

 } else if (pcs->OwningThread == (HANDLE)::GetCurrentThreadId()) {

  // Это не первое обращение, но из той же нити

  ::InterlockedIncrement(&pcs->LockCount);

  pcs->RecursionCount++;

 } else return FALSE; // Критическая секция занята другой нитью

 return TRUE;

}

// Освобождаем критическую секцию

1 ... 139 140 141 142 143 144 145 146 147 ... 156
На этой странице вы можете бесплатно читать книгу Программирование на Visual C++. Архив рассылки - Алекс Jenter бесплатно.
Похожие на Программирование на Visual C++. Архив рассылки - Алекс Jenter книги

Оставить комментарий