Використання потоків, Різне, Програмування, статті

Переклад А.І. Легалова, SoftCraft

Англомовний оригінал знаходиться на сервері компанії Reliable Software

Багатозадачність – один з найбільш важких аспектів програмування. Тому, для неї тим більш важливо забезпечити простий набір абстракцій і інкапсулювати їх в хорошій об’єктно-орієнтованої оболонці. У ГО світі, природним аналогом потоку, який є, чисто процедурної абстракцією, служить “Активний об’єкт”. Активний об’єкт має дзвінком потоком, який асинхронно виконує деякі завдання. Цей потік має доступ до всіх внутрішніх (закритим) даним і методам об’єкта. Відкритий інтерфейс Активного об’єкта доступний зовнішнім агентам (таким як основному потоку, або потоку, який несе повідомлення Windows). Тому, вони можуть маніпулювати станом об’єкта також, як ця маніпуляція здійснюється з утримуваного потоку. Хоча, режим управління при цьому сильно обмежений.

Активний Об’єкт сформований як каркас по імені ActiveObject. Побудова похідного класу, як передбачається, забезпечує реалізацію для чистих віртуальних методів InitThread, Run і Flush (також як і написання деструктора).

class ActiveObject
      {
      public:
          ActiveObject ();
          virtual ~ActiveObject () {}
          void Kill ();
      protected:
          virtual void InitThread () = 0;
          virtual void Run () = 0;
          virtual void FlushThread () = 0;
          static DWORD WINAPI ThreadEntry (void *pArg);
          int             _isDying;
          Thread          _thread;
      };

Конструктор класу ActiveObject ініціалізує утримуваний потік, передаючи йому покажчик функції, яку передбачається виконати і покажчик “this” на себе. Ми повинні відключити попередження, сішналізірующее про використання “this” до повного створення об’єкта. Ми знаємо, що цей об’єкт не буде використовуватися раніше ніж треба, тому що потік створюється в неактивному стані. Передбачається, сто конструктор похідного класу викликає _thread.Resume () щоб активізувати потік.

// The constructor of the derived class
      // should call
      //    _thread.Resume ();
      // at the end of construction
      ActiveObject::ActiveObject ()
      : _isDying (0),
      #pragma warning(disable: 4355) // 'this' used before initialized
        _thread (ThreadEntry, this)
      #pragma warning(default: 4355)
      {
      }

Метод Kill викликає віртуальний метод FlushThread – це необхідно для завершення потоку з будь-якого стану очікування і дає йому можливість запустити _isDying для перевірки прапорця.

void ActiveObject::Kill ()
      {
          _isDying++;
          FlushThread ();
          // Let's make sure it's gone
          _thread.WaitForDeath ();
      }

Ми також маємо каркас для функції ThreadEntry (це – статичний метод класу ActiveObject, тому ми можемо визначати угода про виклики, необхідний API). Ця функція виконується дзвінком потоком. Параметр, що отримується потоком від системи є тим, який ми передали конструктору об’єкта потоку – це покажчик “this” Активного Об’єкту. API очікує void-покажчик, тому ми повинні робити явне приведення покажчика на ActiveObject. Як тільки ми опановуємо Активним Об’єктом, ми викликаємо його чистий віртуальний метод InitThread, робити все специфічні для реалізації приготування, а потім викликаємо основний робочий метод Run. Реалізація методу Run залишена клієнту каркаса.

DWORD WINAPI ActiveObject::ThreadEntry (void* pArg)
      {
          ActiveObject * pActive = (ActiveObject *) pArg;
          pActive->InitThread ();
          pActive->Run ();
          return 0;
      }

Об’єкт Thread – це тонка інкапсуляція API. Зверніть увагу на прапорець CREATE_SUSPENDED, який гарантує, що нитка не почне виконуватися перш, ніж ми не закінчимо конструювання об’єкта ActiveObject.

class Thread
      {
      public:
          Thread ( DWORD (WINAPI * pFun) (void* arg), void* pArg)
          {
              _handle = CreateThread (
                  0, // Security attributes
                  0, // Stack size
                  pFun,
                  pArg,
                  CREATE_SUSPENDED,
                  &_tid);
          }
          ~Thread () { CloseHandle (_handle); }
          void Resume () { ResumeThread (_handle); }
          void WaitForDeath ()
          {
              WaitForSingleObject (_handle, 2000);
          }
      private:
          HANDLE _handle;
          DWORD  _tid;     // thread id
      };

Синхронізація – це те, що дійсно робить багатозадачний режим настільки інтенсивно використовуються. Давайте, почнемо зі взаємних виключень. Клас Mutex – тонка інкапсуляція API. Ви впроваджуєте Mutexes (мутації) в ваш Активний Об’єкт, а потім використовуєте їх через Блокування. Блокування (Lock) – розумний об’єкт, який створюється на стеку. В результаті чого, під час обслуговування, ваш об’єкт захищений від будь-яких інших потоків. Клас Lock – одне з додатків методології Управління ресурсами. Ви повинні помістити Lock всередині всіх методів вашого Активного Об’єкту, які поділяють доступ до даних з іншими потоками.

class Mutex
      {
          friend class Lock;
      public:
          Mutex () { InitializeCriticalSection (& _critSection); }
          ~Mutex () { DeleteCriticalSection (& _critSection); }
      private:
          void Acquire ()
          {
              EnterCriticalSection (& _critSection);
          }
          void Release ()
          {
              LeaveCriticalSection (& _critSection);
          }
          CRITICAL_SECTION _critSection;
      };
      class Lock
      {
      public:
          // Acquire the state of the semaphore
          Lock ( Mutex & mutex )
              : _mutex(mutex)
          {
              _mutex.Acquire();
          }
          // Release the state of the semaphore
          ~Lock ()
          {
              _mutex.Release();
          }
      private:
          Mutex & _mutex;
      };

Подія – це сигнальний пристрій, яке потоки використовують, щоб зв’язатися один з одним. Ви впроваджуєте Подія (Event) в ваш активний об’єкт. Потім Ви перекладаєте утримуваний потік в стан очікування, поки деякий інший потік не звільнить його. Не забудьте однак, що, якщо ваш удерживаемость потік чекає події, він не може бути завершений. Саме тому Ви повинні викликати Release з методу Flush.

class Event
      {
      public:
          Event ()
          {
              // start in non-signaled state (red light)
              // auto reset after every Wait
              _handle = CreateEvent (0, FALSE, FALSE, 0);
          }
          ~Event ()
          {
              CloseHandle (_handle);
          }
          // put into signaled state
          void Release () { SetEvent (_handle); }
          void Wait ()
          {
              // Wait until event is in signaled (green) state
              WaitForSingleObject (_handle, INFINITE);
          }
          operator HANDLE () { return _handle; }
      private:
          HANDLE _handle;
      };

Щоб побачити, як ці класи можуть бути використані, я пропоную невелику підказку. Ви можете перейти до сторінки, яка пояснює, як клас ActiveObject використовується в Частотному аналізаторі для асинхронної модифікації дисплеїв. Або, Ви можете вивчити більш простий приклад Спостерігача Папки, який спокійно чекає, відображаючи папки, і пробуджується тільки, коли присходит зміна її вмісту.

Шкода, що я не можу сказати, що програмування потоків є простим. Однак, воно буде простіше, якщо Ви станете використовувати правильні примітиви. Це примітиви, які я рекламував: ActiveObject, Thread, Mutex, Lock і Event. Деякі з них фактично доступні в MFC. Наприклад, там є блокуючий об’єкт CLock (а може бути – це ГОДИННИК [CLock – гра слів]?) Деструктор якого управляє, критичної секцією (він трохи менш зручний через “двокроковий” конструкції: Ви повинні створити його, а потім вибирати його в два окремих кроку – ніби ви захотіли створити його, а потім передумати). Інша відмінність: MFC пропонує тільки деяку тонку фанеру над API і нічого нового.

Ви можете також розпізнати в деяких з механізмів, які я представив тут, аналоги з мови програмування Java. Звичайно, коли Ви маєте свободу проектування багатозадачного режиму в мові, Ви можете дозволяти собі бути витонченими. Їх версія ActiveObject називається Runnable, і вона має метод run. Кожен об’єкт Java потенційно має вбудований mutex і все, що Вам треба зробити, щоб здійснити блокування, – це оголосити метод (або область) засінхронізірованним. Точно так же події реалізовані з очікуваннями підтвердженнями викликаються всередині будь-якого синхронізованого методу. Тому, якщо Ви знаєте, як програмувати на Java, Ви знаєте, як програмувати на C + + (начебто є знавці Java, необізнані про C + +).

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*