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

Шрифт:

-
+

Интервал:

-
+

Закладка:

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

BOOL InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount);

Заполняют поля структуры, адресуемой lpCriticalSection.

После вызова любой из этих функций критическая секция готова к работе.

Листинг 1. Псевдокод RtlInitializeCriticalSection из ntdll.dll

VOID RtlInitializeCriticalSection(LPRTL_CRITICAL_SECTION pcs) {

 RtlInitializeCriticalSectionAndSpinCount(pcs, 0);

}

VOID RtlInitializeCriticalSectionAndSpinCount(LPRTL_CRITICAL_SECTION pcs, DWORD dwSpinCount) {

 pcs->DebugInfo = NULL;

 pcs->LockCount = -1;

 pcs->RecursionCount = 0;

 pcs->OwningThread = 0;

 pcs->LockSemaphore = NULL;

 pcs->SpinCount = dwSpinCount;

 if (0x80000000 & dwSpinCount) _CriticalSectionGetEvent(pcs);

}

DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount);

Устанавливает значение поля SpinCount и возвращает его предыдущее значение. Напоминаю, что старший бит отвечает за "привязку" события, используемого для ожидания доступа к данной критической секции.

Листинг 2. Псевдокод RtlSetCriticalSectionSpinCount из ntdll.dll

DWORD RtlSetCriticalSectionSpinCount(LPRTL_CRITICAL_SECTION pcs, DWORD dwSpinCount) {

 DWORD dwRet = pcs->SpinCount;

 pcs->SpinCount = dwSpinCount;

 return dwRet;

}

VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

Освобождает ресурсы, занимаемые критической секцией.

Листинг 3. Псевдокод RtlDeleteCriticalSection из ntdll.dll

VOID RtlDeleteCriticalSection(LPRTL_CRITICAL_SECTION pcs) {

 pcs->DebugInfo = NULL;

 pcs->LockCount = -1;

 pcs->RecursionCount = 0;

 pcs->OwningThread = 0;

 if (pcs->LockSemaphore) {

  ::CloseHandle(pcs->LockSemaphore);

  pcs->LockSemaphore = NULL;

 }

}

VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

Осуществляют "захват" критической секции. Если критическая секция занята другой нитью, то ::EnterCriticalSection() будет ждать, пока та освободится, а ::TryEnterCriticalSection() вернет FALSE.

Листинг 4. Псевдокод RtlEnterCriticalSection из ntdll.dll

VOID RtlEnterCriticalSection(LPRTL_CRITICAL_SECTION pcs) {

 if (::InterlockedIncrement(&pcs->LockCount)) {

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

   pcs->RecursionCount++;

   return;

  }

  RtlpWaitForCriticalSection(pcs);

 }

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

 pcs->RecursionCount = 1;

}

BOOL RtlTryEnterCriticalSection(LPRTL_CRITICAL_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;

}

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

 // ...   

 ::EnterCriticalSection(&m_lock1);

 // ...   

 ::LeaveCriticalSection(&m_lock1);

 // ...   

 ::LeaveCriticalSection(&m_lock2);

}

Еще могут возникнуть проблемы при… копировании критических секций. Понятно, что вот такой код вряд ли сможет написать программист в здравом уме и памяти:

CRITICAL_SECTION sec1;

CRITICAL_SECTION sec2;

// …

sec1 = sec2;

Из такого присвоения трудно извлечь какую-либо пользу. А вот такой код иногда пишут:

struct SData {

 CLock _lock;

 DWORD m_dwSmth;

} m_data;

void Proc1(SData& data) {

 m_data = data;

}

и все бы хорошо, если бы у структуры SData был конструктор копирования, например такой:

SData(const SData data) {

 CScopeLock lock(data.m_lock);

 m_dwSmth = data.m_dwSmth;

}

но нет, программист посчитал, что хватит за глаза простого копирования полей и, в результате, переменная m_lock была просто скопирована, хотя именно в этот момент из другой нити она была "захвачена" и значение поля LockCount у нее в этот момент больше либо равен нулю. После вызова ::LeaveCriticalSection() в той нити, у исходной переменной m_lock значение поля LockCount уменьшилось на единицу. А у скопированно переменной – осталось прежним. И любой вызов ::EnterCriticalSection() в этой нити никогда не вернется. Он будет вечно ждать неизвестно чего.

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

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