ПЕРЕЛІК ДИСЦИПЛІН:
  • Адміністративне право
  • Арбітражний процес
  • Архітектура
  • Астрологія
  • Астрономія
  • Банківська справа
  • Безпека життєдіяльності
  • Біографії
  • Біологія
  • Біологія і хімія
  • Ботаніка та сільське гос-во
  • Бухгалтерський облік і аудит
  • Валютні відносини
  • Ветеринарія
  • Військова кафедра
  • Географія
  • Геодезія
  • Геологія
  • Етика
  • Держава і право
  • Цивільне право і процес
  • Діловодство
  • Гроші та кредит
  • Природничі науки
  • Журналістика
  • Екологія
  • Видавнича справа та поліграфія
  • Інвестиції
  • Іноземна мова
  • Інформатика
  • Інформатика, програмування
  • Юрист по наследству
  • Історичні особистості
  • Історія
  • Історія техніки
  • Кибернетика
  • Комунікації і зв'язок
  • Комп'ютерні науки
  • Косметологія
  • Короткий зміст творів
  • Криміналістика
  • Кримінологія
  • Криптология
  • Кулінарія
  • Культура і мистецтво
  • Культурологія
  • Російська література
  • Література і російська мова
  • Логіка
  • Логістика
  • Маркетинг
  • Математика
  • Медицина, здоров'я
  • Медичні науки
  • Міжнародне публічне право
  • Міжнародне приватне право
  • Міжнародні відносини
  • Менеджмент
  • Металургія
  • Москвоведение
  • Мовознавство
  • Музика
  • Муніципальне право
  • Податки, оподаткування
  •  
    Бесплатные рефераты
     

     

     

     

     

     

         
     
    Критичні секції
         

     

    Інформатика, програмування

    Критичні секції

    Павло Блудов

    Введення

    Критичні секції - це об'єкти, що використовуються для блокування доступу всіх ниток (threads) додатка, крім однієї, до деяких важливих даних в один момент часу. Наприклад, є мінлива m_pObject і кілька ниток, що викликають методи об'єкта, на який посилається m_pObject, причому ця змінна може змінювати своє значення час від часу. Іноді там навіть виявляється нуль. Припустимо, є ось такий код:        

    // Нитка № 1   

    void Proc1 ()   

    (   

    if (m_pObject)   

      m_pObject-> SomeMethod ();   

    )      

    // Нитка № 2   

    void Proc2 (IObject * pNewObject)   

    (   

    if (m_pObject)   

    delete m_pObject;   

    m_pObject = pNewobject;   

    )     

    Тут ми маємо потенційну небезпеку виклику m_pObject-> SomeMethod () після того, як об'єкт був знищений за допомогою delete m_pObject. Справа в тому, що в системах з витісняючої багатозадачністю виконання будь-якої нитки процесу може перерватися в самий невідповідний для неї момент часу, і почне виконуватися зовсім інша нитка. У даному прикладі невідповідним моментом буде той, у якому нитка № 1 вже перевірила m_pObject, але ще не встигла викликати SomeMethod (). Виконання нитки № 1 урвалася, і почала виконуватися нитку № 2. Причому нитка № 2 встигла викликати деструктор об'єкта. Що ж станеться, коли нитка № 1 отримає трохи процесорного часу і викличе-таки SomeMethod () у вже неіснуючого об'єкта? Напевне, щось жахливе.

    Саме тут приходять на допомогу критичні секції. Перепишемо наш приклад.        

    // Нитка № 1   

    void Proc1 ()   

    (   

      :: EnterCriticalSection (& m_lockObject);   

    if (m_pObject)   

      m_pObject-> SomeMethod ();   

      :: LeaveCriticalSection (& m_lockObject);   

    )      

    // Нитка № 2   

    void Proc2 (IObject * pNewObject)   

    (   

      :: EnterCriticalSection (& m_lockObject);   

    if (m_pObject)   

    delete m_pObject;   

    m_pObject = pNewobject;   

    :: LeaveCriticalSection (& m_lockObject);   

    )     

    Код, поміщений між:: EnterCriticalSection () і :: LeaveCriticalSection () з однієї і тієї ж критичною секцією як параметра, ніколи не буде виконуватися паралельно. Це означає, що якщо нитка № 1 встигла "захопити" критичну секцію m_lockObject, то при спробі нитки № 2 дістати цю ж критичну секцію у своє одноосібне користування, її виконання буде припинено до тих пір, поки нитка № 1 не "відпустить" m_lockObject за допомогою виклику:: LeaveCriticalSection (). І навпаки, якщо нитка № 2 встигла раніше нитки № 1, то та "зачекає", перш ніж почне роботу з m_pObject.

    Робота з критичними секціями

    Що ж відбувається всередині критичних секцій і як вони влаштовані? Перш за все, слід відзначити, що критичні секції - це не об'єкти ядра операційної системи. Практично вся робота з критичними секціями відбувається в Який створив їх процесі. З цього випливає, що критичні секції можуть бути використані тільки для синхронізації в межах одного процесу. Тепер розглянемо критичні секції ближче.

    Структура RTL_CRITICAL_SECTION        

    typedef struct _RTL_CRITICAL_SECTION (   

    PRTL_CRITICAL_SECTION_DEBUG   DebugInfo;// Використовується операційною системою   

    LONG LockCount;// Счетчик використання цієї   критичної секції   

    LONG RecursionCount;// Счетчик повторного захоплення з   нитки-власника   

    HANDLE OwningThread;// Унікальний ID нитки-власника   

    HANDLE LockSemaphore;// Об'єкт ядра використовується для очікування   

    ULONG_PTR SpinCount;// Кількість холостих циклів перед   викликом ядра   

    ) RTL_CRITICAL_SECTION, * PRTL_CRITICAL_SECTION;     

    Поле LockCount збільшується на одиницю при кожному виклик:: EnterCriticalSection () і зменшується при кожному виклику :: LeaveCriticalSection (). Це перший (а часто і єдина перевірка) на шляху до "захоплення" критичної секції. Якщо після збільшення в цьому полі знаходиться нуль, це означає, що до цього моменту непарних викликів:: EnterCriticalSection () з інших ниток не було. У цьому випадку можна забрати дані, що охороняються цієї критичною секцією у монопольне користування. Таким чином, якщо критична секція інтенсивно використовується не більш ніж однієї ниткою, :: EnterCriticalSection () практично вироджується в + + LockCount, а :: LeaveCriticalSection () в - LockCount. Це дуже важливо. Це означає, що використання багатьох тисяч критичних секцій в одному процесі не спричинить значної витрати ні системних ресурсів, ні процесорного часу.        

    РАДА   

    Не варто економити на критичних   секціях. Багато cекономіть все одно не вийде.     

    У полі RecursionCount зберігається кількість повторних викликів:: EnterCriticalSection () з однієї і тієї ж нитки. Дійсно, якщо спричинити:: EnterCriticalSection () з однієї і тієї ж нитки кілька разів, все виклики будуть успішні. Тобто ось такий код не зупиниться навічно в другому виклик:: EnterCriticalSection (), а відпрацює до кінця.        

    // Нитка № 1   

    void Proc1 ()   

    (   

      :: EnterCriticalSection (& m_lock);   

    //. ..   

    Proc2 ()   

    //. ..   

      :: LeaveCriticalSection (& m_lock);   

    )      

    // Все ще нитка № 1   

    void Proc2 ()   

    (   

      :: EnterCriticalSection (& m_lock);   

    //. ..   

    :: LeaveCriticalSection (& m_lock);   

    )     

    Дійсно, критичні секції призначені для захисту даних від доступу з декількох ниток. Багаторазове використання однієї і тієї ж критичної секції з однієї нитки не призведе до помилки. Це цілком нормальне явище. Слідкуйте, щоб кількість викликів:: EnterCriticalSection () і:: LeaveCriticalSection () збігалося, і все буде добре.

    Поле OwningThread містить 0 для ніким не зайнятих критичних секцій або унікальний ідентифікатор нитки-власника. Це поле перевіряється, якщо при виклику:: EnterCriticalSection () поле LockCount після збільшення на одиницю виявилося більше нуля. Якщо OwningThread збігається з унікальним ідентифікатором поточної нитки, то RecursionCount просто збільшується на одиницю і:: EnterCriticalSection () повертається негайно. Інакше :: EnterCriticalSection () буде чекати, поки нитка, що володіє критичної секцією, не викличе:: LeaveCriticalSection () необхідну кількість разів.

    Поле LockSemaphore використовується, якщо потрібно почекати, поки критична секція звільниться. Якщо LockCount більше нуля, і OwningThread не співпадає з унікальним ідентифікатором поточної нитки, то що чекає нитка створює об'єкт ядра (подія) і викликає:: WaitForSingleObject (LockSemaphore). Нитка-власник, після зменшення RecursionCount, перевіряє його, і якщо значення цього поля дорівнює нулю, а LockCount більше нуля, то це означає, що є як мінімум одна нитка, яка чекає, поки LockSemaphore не опиниться в стані "сталося!". Для цього нитка-власник викликає:: SetEvent (), і какая-то один (тільки один) з очікують ниток пробуджується і отримує доступ до критичним даними.

    WindowsNT/2k генерує виключення, якщо спроба створити подія не увінчалася успіхом. Це вірно як для функцій :: Enter/LeaveCriticalSection (), так і для :: InitializeCriticalSectionAndSpinCount () з встановленим старшим бітом параметра SpinCount. Але тільки не в WindowsXP. Розробники ядра цієї операційної системи вчинили по-іншому. Замість генерації виключення, функції :: Enter/LeaveCriticalSection (), якщо не можуть створити власну подія, починають використовувати заздалегідь створений глобальний об'єкт. Один на всіх. Таким чином, у разі катастрофічної нестачі системних ресурсів, програма під управлінням WindowsXP шкандибає якийсь час далі. Дійсно, писати програми, здатні продовжувати працювати після того, як :: EnterCriticalSection () сгенерирована виняток, надзвичайно складно. Як правило, якщо програмістом і передбачено такий поворот подій, то далі виводу повідомлення про помилку та аварійного завершення програми справа не йде. Як наслідок, WindowsXP ігнорує старший біт поля LockCount.

    І, нарешті, поле SpinCount. Це поле використовується тільки багатопроцесорними системами. У однопроцесорних системах, якщо критична секція зайнята іншою ниткою, можна лише перемкнути керування на неї і почекати настання події. У багатопроцесорних системах є альтернатива: прогнати деяка кількість разів холостий цикл, перевіряючи кожен раз, не звільнилася чи наша критична секція. Якщо за SpinCount раз це не вийшло, переходимо до очікування. Це набагато ефективніше, ніж перемикання на планувальник ядра і назад. Крім того, в WindowsNT/2k старший біт цього поля служить для індикації того, що об'єкт ядра, хендл якого знаходиться в полі LockSemaphore, повинен бути створений заздалегідь. Якщо системних ресурсів для цього недостатньо, система згенерує виключення, і програма може "урізати" свою функціональність. Або зовсім завершити роботу.        

    ПРИМІТКА   

    Все це вірно для Windows NT/2k/XP. У   Windows 9x/Me використовується тільки поле LockCount. Там знаходиться вказівник на   об'єкт ядра, можливо, просто взаимовиключення (mutex). Всі інші поля   дорівнюють нулю.     

    API для роботи з критичними секціями

    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. Відсутній в Windows 9x/ME.

    Лістинг 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_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 m_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, і жодних проблем не виникло. Сильно надуманий приклад? Анітрохи. Саме перемикання ниток лежить в основі підрозділів (apartments) COM. А з цього випливає одне дуже, дуже неприємне правило.        

    РАДА   

    Уникайте дзвінків яких би то не було   об'єктів при захоплених критичних секціях.     

    Пам'ятайте приклад з початку статті? Так ось, він абсолютно неприйнятний у подібних випадках. Його доведеться переробити на щось подібне до прикладу, наведеного в лістингу 12.

    Лістинг 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 ():

    Лістинг 13        

    // Нитка № 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, яке завжди дорівнює нулю і, судячи з усього, не використовується. Зате використовується ключ в реєстрі "HKLMSYSTEMCurrentControlSetControlSession ManagerCriticalSectionTimeout ", який за замовчуванням дорівнює 30 діб, і по закінченню цього часу в системний лог потрапляє рядок "RTL: Enter Critical Section Timeout (2 minutes) nRTL: Pid.Tid XXXX.YYYY, owner tid ZZZZnRTL: Re-Waitingn ". До того ж це вірно тільки для систем WindowsNT/2k/XP і тільки з CheckedBuild. У вас встановлено CheckedBuild? Ні? А даремно. Ви втрачаєте виняткову можливість побачити цю чудову рядок.

    Ну, а які у нас альтернативи? Так, мабуть, тільки один. Не використовувати API для роботи з критичними секціями. Замість них написати свої власні. Нехай навіть не такі обточені напильником, як у Windows NT. Не страшно. Нам це знадобиться тільки в debug-конфігураціях. У release'ах ми будемо продовжувати використовувати оригінальний API від Microsoft. Для цього напишемо кілька функцій, повністю сумісних за типами та кількістю аргументів з "справжнім" API, і додамо # define, як у MFC, для перевизначення оператора new в debug-конфігураціях.

    Лістинг 14. Власна реалізація критичних секцій.        

    # if defined (_DEBUG) & &! defined (_NO_DEADLOCK_TRACE)      

    # define DEADLOCK_TIMEOUT 30000   

    # define CS_DEBUG 1      

    // Створюємо на льоту подія   для операцій очікування,   

    // але ніколи його не   звільняємо. Так   зручніше для налагодження   

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

    (   

    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 ", DEADLOCK_TIMEOUT,   

      :: GetCurrentThreadId (), pcs-> OwningThread);   

    )   

    ) 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 EnterCriticalSectionDbg (LPCRITICAL_SECTION pcs)   

    (   

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

    (   

    // LockCount став більше нуля.   

    // Перевіряємо ідентифікатор нитки   

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

    (   

    // Нитка та ж сама. Критична секція   наша.   

    pcs-> RecursionCount ++;   

    return;   

    )      

    // Критична секція зайнята інший   ниткою.   

    // Доведеться почекати   

    _WaitForCriticalSectionDbg (pcs);   

    )      

    // Або критична секція була   "вільна",   

    // або ми дочекалися. Зберігаємо   ідентифікатор поточної нитки.   

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

    Лістинг 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).

    Лістинг 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;   

    )      

    // Звільняємо критичну секцію   

    inline VOID LeaveCriticalSectionDbg (LPCRITICAL_SECTION_DBG pcs)   

    (   

    // Перевіряємо, щоб ідентифікатор поточної   нитки з?? впадав   

    // з ідентифікатором нитки-влядельца.   

    // Якщо це не так, швидше за все ми маємо   справу з помилкою   

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

    if (- pcs-> RecursionCount)   

    (   

    // Не останній виклик з цієї нитки.   

    // Зменшуємо значення поля LockCount   

      :: InterlockedDecrement (& pcs-> LockCount);   

    )   

    else   

    (   

    // Останній дзвінок. Потрібно   "розбудити" яку-небудь   

    // з очікують ниток, якщо такі   є   

    ATLASSERT (NULL! = pcs-> OwningThread);      

    pcs-> OwningThread = NULL;   

    pcs-> m_nLine = 0;   

    pcs-> m_azFile = 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 InitializeCriticalSection InitializeCriticalSectionDbg   

    # define InitializeCriticalSectionAndSpinCount (pcs, c)   

      InitializeCriticalSectionDbg (pcs)   

    # define DeleteCriticalSection DeleteCriticalSectionDbg   

    # define EnterCriticalSection (pcs) EnterCriticalSectionDbg (pcs,   __LINE__, __FILE__)   

    # define TryEnterCriticalSection (pcs)   

      TryEnterCriticalSectionDbg (pcs, __LINE__, __FILE__)   

    # define LeaveCriticalSection LeaveCriticalSectionDbg   

    # define CRITICAL_SECTION CRITICAL_SECTION_DBG   

    # define LPCRITICAL_SECTION LPCRITICAL_SECTION_DBG   

    # define PCRITICAL_SECTION PCRITICAL_SECTION_DBG      

    # endif     

    Наводимо наші класи у відповідність (лістинг +17).

    Лістинг 17. Класи CLock і CScopeLock, варіант для налагодження.        

    class CLock   

    (   

    friend class CScopeLock;   

    CRITICAL_SECTION m_CS;   

    public:   

    void Init () (   :: InitializeCriticalSection (& m_CS);)   

    void Term () (   :: DeleteCriticalSection (& m_CS);)      

    # if defined (CS_DEBUG)   

    BOOL Check () (return   CheckCriticalSection (& m_CS);)   

    # endif   

    # if CS_DEBUG> 1   

    void Lock (int nLine, LPSTR   azFile) (EnterCriticalSectionDbg (& m_CS, nLine, azFile);)   

    BOOL TryLock (int nLine, LPSTR   azFile) (return TryEnterCriticalSectionDbg (& m_CS, nLine, azFile);)   

    # else   

    void Lock () (   :: EnterCriticalSection (& m_CS);)   

    BOOL TryLock () (return   :: TryEnterCriticalSection (& m_CS);)   

    # endif   

    void Unlock () (   :: LeaveCriticalSection (& m_CS);)   

    );   

    class CScopeLock   

    (   

    LPCRITICAL_SECTION m_pCS;   

    public:   

    # if CS_DEBUG> 1   

    CScopeLock (LPCRITICAL_SECTION   pCS, int nLine, LPSTR azFile): m_pCS (pCS) (Lock (nLine, azFile);)   

    CScopeLock (CLock & lock,   int nLine, LPSTR azFile): m_pCS (& lock.m_CS) (Lock (nLine, azFile);)   

    void Lock (int nLine, LPSTR   azFile) (EnterCriticalSectionDbg (m_pCS, nLine, azFile);)   

    # else   

    CScopeLock (LPCRITICAL_SECTION   pCS): m_pCS (pCS) (Lock ();)   

    CScopeLock (CLock & lock):   m_pCS (& lock.m_CS) (Lock ();)   

    void Lock () (:: EnterCriticalSection (m_pCS);   )   

    # endif   

    ~ CScopeLock () (Unlock ();)   

    void Unlock () (   :: LeaveCriticalSection (m_pCS);)   

    );      

    # if CS_DEBUG> 1   

    # define Lock () Lock (__LINE__, __FILE__)   

    # define TryLock () TryLock (__LINE__, __FILE__)   

    # define lock (cs) lock (cs, __LINE__, __FILE__)   

    # endif     

    На жаль, довелося навіть перевизначити CScopeLock lock (cs), причому жорстко прив'язатися до імені змінної. Не варто говорити про те, що напевно вийшов конфлікт імен - все-таки Lock досить популярне назва для методу. Такий код не буде збиратися, наприклад, з популярною бібліотекою ATL. Тут є два способи. Перейменувати методи Lock () і TryLock () у що-небудь більш унікальне, або перейменувати Lock () в ATL:        

    // StdAfx.h   

    //. ..   

    # define Lock ATLLock   

    # include   

    //. ..     

    Сме

         
     
         
    Реферат Банк
     
    Рефераты
     
    Бесплатные рефераты
     

     

     

     

     

     

     

     
     
     
      Все права защищены. Reff.net.ua - українські реферати ! DMCA.com Protection Status