Алекс Jenter - Программирование на Visual C++. Архив рассылки
- Название:Программирование на Visual C++. Архив рассылки
- Автор:
- Жанр:
- Издательство:неизвестно
- Год:неизвестен
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Алекс Jenter - Программирование на Visual C++. Архив рассылки краткое содержание
РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN, НА САЙТЕ КОТОРОГО ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ, РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.
Программирование на Visual C++. Архив рассылки - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
И, наконец, поле SpinCount. Это поле используется только многопроцессорными системами. В однопроцессорных системах, если критическая секция занята другой нитью, можно только переключить управление на нее и подождать наступления нашего события. В многопроцессорных системах есть альтернатива: прогнать некоторое количество раз холостой цикл, проверяя каждый раз, не освободилась ли наша критическая секция. Если за SpinCount раз это не получилось, переходим к ожиданию. Это гораздо эффективнее, чем переключение на планировщик ядра и обратно. Кроме того, в WindowsNT/2k старший бит этого поля служит для индикации того, что объект ядра, хендл которого находится в поле LockSemaphore, должен быть создан заранее. Если системных ресурсов для этого недостаточно, система сгенерирует исключение, и программа может "урезать" свою функциональнось. Или совсем завершить работу.
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, но для экономии места называл эту переменную одной буквой:
Читать дальшеИнтервал:
Закладка: