Алекс Jenter - Программирование на Visual C++. Архив рассылки
- Название:Программирование на Visual C++. Архив рассылки
- Автор:
- Жанр:
- Издательство:неизвестно
- Год:неизвестен
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Алекс Jenter - Программирование на Visual C++. Архив рассылки краткое содержание
РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN, НА САЙТЕ КОТОРОГО ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ, РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.
Программирование на Visual C++. Архив рассылки - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
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() в этой нити никогда не вернется. Он будет вечно ждать неизвестно чего.
Это только цветочки. С ягодками Вы очень быстро столкнетесь, если попытаетесь написать что-нибудь действительно сложное. Например, ActiveX-объект в многопоточном подразделении (MTA), создаваемый из скрипта, запущенного из-под контейнера, размещенного в однопоточном подразделении (STA). Ни слова не понятно? Не беда. Сейчас я попытаюсь выразить проблему более понятным языком. Итак. Имеется объект, вызывающий методы другого объекта, причем живут они в разных нитях. Вызовы производятся синхронно. Т.е. объект #1 переключает выполнение на нить объекта #2, вызывает метод и переключается обратно на свою нить. При этом выполнение нити #1 приостановлено до тех пор, пока не отработает нить объекта #2. Теперь положим, объект #2 вызывает метод объекта #1 из своей нити. Получается, что управление вернулось в объект #1, но из нити объекта #2. Если объект #1 вызывал метод объекта #2, захватив какую-либо критическую секцию, то при вызове метода объекта #1 тот заблокирует сам себя при повторном входе в ту же критическую секцию.
Листинг 11. Самоблокировка средствами одного объекта
// Нить #1
void IObject1::Proc1() {
// Входим в критическую секцию объекта #1
m_lockObject.Lock();
// Вызываем метод объекта #2, происходит переключение на нить объекта #2
m_pObject2->SomeMethod();
// Сюда мы попадем только по возвращении из
m_pObject2->SomeMethod();
m_lockObject.Unlock();
}
// Нить #2
void IObject2::SomeMethod() {
// Вызываем метод объекта #1 из нити объекта #2
m_pObject1->Proc2();
}
// Нить #2
void IObject1::Proc2() {
// Пытаемся войти в критическую секцию объекта #1
m_lockObject.Lock();
// Сюда мы не попадем никогда
m_lockObject.Unlock();
}
Если бы в примере не было переключения нитей, все вызовы произошли бы в нити объекта #1, и никаких проблем не возникло. Сильно надуманный пример? Ничуть. Именно переключение ниток лежит в основе подразделений COM (apartments). А из этого следует одно очень, очень неприятное правило.
СОВЕТ
Избегайте вызовов каких бы то ни было объектов при захваченных критических секциях.
Помните пример из начала статьи? Так вот, он абсолютно неприемлем в подобных случаях. Его придется переделать на что-то вроде
Листинг 12. Простой пример, не подверженный самоблокировке
// Нить #1
void Proc1() {
m_lockObject.Lock();
CComPtr pObject(m_pObject); // вызов pObject->AddRef();
m_lockObject.Unlock();
if (pObject) pObject->SomeMethod();
}
// Нить #2
void Proc2(IObject *pNewObject) {
m_lockObject.Lock();
m_pObject = pNewobject;
m_lockObject.Unlock();
}
Доступ к объекту остался по-прежнему синхронизован, но вызов SomeMethod(); происходит вне критической секции. Победа? Почти. осталась одна маленькая деталь. Давайте посмотрим, что происходит в Proc2():
void Proc2(IObject *pNewObject) {
m_lockObject.Lock();
if (m_pObject.p) m_pObject.p->Release();
m_pObject.p = pNewobject;
if (m_pObject.p) m_pObject.p->AddRef();
m_lockObject.Unlock();
}
Очевидно, что вызовы m_pObject.p->AddRef(); и m_pObject.p->Release(); происходят внутри критической секции. И если вызов метода AddRef(), как правило, безвреден, то вызов метода Release() может оказаться последнимвызовом Release(), и объект самоуничтожится. В методе FinalRelease() объекта #2 может быть все что угодно, например, освобождение объектов, живущих в других подразделениях. А это опять приведет к переключению ниток и может вызвать самоблокировку объекта #1 по уже известному сценарию. Придется воспользоваться той же техникой, что и в методе Proc1().
// Нить #2 void Proc2(IObject *pNewObject) {
CComPtr pPrevObject;
m_lockObject.Lock();
pPrevObject.Attach(m_pObject.Detach());
m_pObject = pNewobject;
m_lockObject.Unlock(); // pPrevObject.Release();
}
Теперь потенциально последний вызов IObject2::Release() будет осуществлен после выхода из критической секции. А присвоение нового значения по-прежнему синхронизовано с вызовом IObject2::SomeMethod() из нити #1.
Сначала стоит обратить внимание на "официальный" способ обнаружения блокировок. Если бы кроме ::EnterCriticalSection() и ::TryEnterCtiticalSection() существовал бы еще и ::EnterCriticalSectionWithTimeout(), то достаточно было бы просто указать какое-нибудь резонное значение для интервала ожидания, например, 30 секунд. Если критическая секция не освободилась в течение указанного времени, то с очень большой вероятностью она не освободится никогда. Имеет смысл подключить отладчик и посмотреть, что же творится в соседних нитьх. Но увы. Никаких ::EnterCriticalSectionWithTimeout() в Win32 не предусмотрено. Вместо этого есть поле CriticalSectionDefaultTimeoutв структуре IMAGE_LOAD_CONFIG_DIRECTORY32, которое всегда равно нулю и, судя по всему, не используется. Зато используется ключ в реестре "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\CriticalSectionTimeout", который по умолчанию равен 30 суткам, и по истечению этого времени в системный лог попадает строка "RTL: Enter Critical Section Timeout (2 minutes)\nRTL: Pid.Tid XXXX.YYYY, owner tid ZZZZ\nRTL: Re-Waiting\n". К тому же это верно только для систем WindowsNT/2k/XP и только с CheckedBuild. У вас установлен CheckedBuild? Нет? А зря. Вы теряете исключительную возможность увидеть эту замечательную строку.
Читать дальшеИнтервал:
Закладка: