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

ЖАНРЫ

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

Jenter Алекс

Шрифт:

 } 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; }

 ~CScopeLock { Unlock; }

 void Lock { ::EnterCriticalSection(m_pCS); }

 void Unlock { ::LeaveCriticalSection(m_pCS); }

};

Классы CLock и CAutoLock удобно использовать для синхронизации доступа к переменным класса, а CScopeLock предназначен, в основном, для использования в процедурах. Удобно, что компилятор сам позаботится о вызове ::LeaveCriticalSection через наш деструктор.

Листинг 7. Пример использования CScopeLock

CAutoLock m_lockObject;

CObject *m_pObject;

void Proc1 {

 CScopeLock lock(m_lockObject); // Вызов lock.Lock;

 if (!m_pObject) return; // Вызов lock.Unlock;

 m_pObject->SomeMethod;

 // Вызов lock.Unlock;

}

Отладка критических секций

Весьма интересное и увлекательное занятие. Можно потратить часы и недели, но так и не найти, где именно возникает проблема. Стоит уделить этому особо пристальное внимание. Ошибки, связанные с критическими секциями бывают двух типов: ошибки реализации и архитектурные ошибки.

Ошибки, связанные с реализацией

Это довольно легко обнаруживаемые ошибки, как правило, связанные с непарностью

вызовов ::EnterCriticalSection и ::LeaveCriticalSection.

Листинг 8. Пропущен вызов ::EnterCriticalSection

// Процедура предполагает, что m_lockObject.Lock; уже был вызван

void Pool {

 for (int i = 0; i < m_vectSinks.size; i++) {

m_lockObject.Unlock;

m_vectSinks[i]->DoSomething;

m_lockObject.Lock;

 }

}

::LeaveCriticalSection без ::EnterCriticalSection приведет к тому, что первый же вызов ::EnterCriticalSection остановит выполнение нити навсегда.

Листинг 9. Пропущен вызов ::LeaveCriticalSection

void Proc {

 m_lockObject.Lock;

 if (!m_pObject) return;

 // ...

 m_lockObject.Unlock;

}

В этом примере, конечно, имеет смысл воспользоваться классом типа CScopeLock.

Кроме того, случается, что ::EnterCriticalSection вызывается без инициализации критической секции с помощью ::InitializeCriticalSection. Особенно часто такое случается с проектами, написанными с помощью ATL. Причем в debug-версии все работает замечательно, а release-версия рушится. Это происходит из-за так называемой "минимальной" CRT (_ATL_MIN_CRT), которая не вызывает конструкторы статических объектов (Q166480, Q165076). В ATL версии 7.0 эту проблему решили.

Еще я встречал такую ошибку: программист пользовался классом типа CScopeLock, но для экономии места называл эту переменную одной буквой:

CScopeLock l(m_lock);

и как-то раз просто пропустил имя у переменной. Получилось

CScopeLock (m_lock);

а что это означает? Компилятор честно сделал вызов конструктора CScopeLock, и тут же уничтожил этот безымянный объект, как и положено по стандарту. Т.е. сразу же после вызова метода Lock последовал вызов Unlock, и синхронизация перестала иметь место. Вообще, давать переменным, даже локальным, имена из одной буквы – путь быстрого наступления на всяческие грабли.

СОВЕТ

Если у Вас в процедуре больше одного цикла, то вместо int i, j, k стоит все-таки использовать что-то вроде int nObject, nSection, nRow.

Архитектурные ошибки

Самая известная из них это блокировка (deadlock) когда две нити пытаются захватить две или более критических секций, причем делают это в разном порядке.

Листинг 10. Взаимоблокировка двух ниток

void Proc1 // Нить #1

{

 ::EnterCriticalSection(&m_lock1);

 // ...

 ::EnterCriticalSection(&m_lock2);

 // ...

 ::LeaveCriticalSection(&m_lock2);

 // ...

 ::LeaveCriticalSection(&m_lock1);

}

// Нить #2

void Proc2 {

 ::EnterCriticalSection(&m_lock2);

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