1   2   3   4   5   6   7   8   9   10
Ім'я файлу: Отчет.docx
Розширення: docx
Розмір: 396кб.
Дата: 08.06.2021
скачати

Процеси

  1. Визначення процесу


Багатозадачність на основі потоків найчастіше організовується при програмуванні на С#. Але там, де це доречно, можна організувати і багатозадачність на основі процесів. У цьому випадку замість запуску іншого потоку в одній і тій же програмі одна програма починає виконання іншої. При програмуванні на C# це робиться за допомогою класу Process, визначеного в просторі імен System.Diagnostics.

Єдиний потік також в будь-який момент може бути переміщений в певний контекст, і він може переміщатися в межах нового контексту за примхою CLR. Для отримання поточного контексту, в якому виконується потік, використовуйте статичну властивість Thread.CurrentContext [1].

У Windows під процесом розуміється об'єкт ядра, якому належать системні ресурси, використовувані виконуваним додатком. Тому можна сказати, що в Windows процесом є додаток, що виконується. Виконання кожного процесу починається з первинного потоку. Під час свого виконання процес може створювати інші потоки. Виконання процесу закінчується при завершенні роботи всіх його потоків. Кожний процес в операційній системі Windows володіє наступними ресурсами:

  • віртуальним адресним простором;

  • робочою безліччю сторінок в реальній пам'яті;

  • маркером доступу, що містить інформацію для системи безпеки;

  • таблицею для зберігання дескрипторів об'єктів ядра.

Крім дескриптора, кожен процес в Windows має свій ідентифікатор, який є унікальним для процесів, що виконуються в системі. Ідентифікатори процесів використовуються, головним чином, службовими програмами, які дозволяють користувачам системи відстежувати роботу процесів [2].

Іноді процесу потрібно знати свій дескриптор, щоб змінити якісь свої характеристики. Наприклад, процес може змінити свій пріоритет.

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

      1. Запуск процесу


Найпростіший спосіб запустити інший процес - скористатися методом Start(), визначених у класі Process. Нижче наведена одна з найпростіших форм цього методу:

public static Process Start (string имя_файла)

де имя_файла позначає конкретне ім'я файлу, який повинен виконуватися або ж пов'язаний з виконуваним файлом.

Процес, який створює новий процес, називається батьківським процесом (parent process) по відношенню до створюваного процесу. Новий же процес, який створюється іншим процесом, називається дочірнім процесом (child process) по відношенню до процесу-батькові [2].

      1. Завершення процесу


Коли створений процес завершується, слід викликати метод Close(), щоб звільнити пам'ять, виділену для цього процесу. Нижче наведена форма оголошення методу Close().

public void Close()

    1. Синхронізація

      1. Визначення синхронізації


Синхронізація процесів - це є досягнення деякого фіксованого порядку між сигналами, якими обмінюються ці процеси. У програмуванні розглядаються паралельні процеси, які є програмами, що виконуються процесором [2].

Усі потоки, що належать одному процесу, поділяють деякі загальні ресурси - такі, як адресний простір оперативної пам'яті або відкриті файли. Ці ресурси належать всьому процесу, а значить, і кожному його потоку. Отже, кожний потік може працювати з цими ресурсами без будь-яких обмежень. Але ... Якщо один потік ще не закінчив працювати з будь-яким загальним ресурсом, а система переключилася на інший потік, який використовує цей же ресурс, то результат роботи цих потоків може надзвичайно сильно відрізнятися від задуманого. Такі конфлікти можуть виникнути і між потоками, що належать різним процесам. Завжди, коли два або більше потоків використовують будь-якої загальний ресурс, виникає ця проблема [1].

Саме тому необхідний механізм, що дозволяє потокам погоджувати свою роботу з загальними ресурсами. Цей механізм отримав назву механізму синхронізації потоків.

Цей механізм являє собою набір об'єктів операційної системи, які створюються і управляються програмно, є загальними для всіх потоків в системі (деякі - для потоків, що належать одному процесу) і використовуються для координування доступу до ресурсів. Як ресурси може виступати все, що може бути загальним для двох і більше потоків: файл на диску, порт, запис в базі даних, об'єкт GDI, і навіть глобальна змінна програми (яка може бути доступна з потоків, що належать одному процесу).

Об'єктів синхронізації існує кілька, найважливіші з них:

  • взаємовиключення (mutex),

  • подія (event),

  • семафор (semaphore).

Кожен з цих об'єктів реалізує свій спосіб синхронізації. Також в якості об'єктів синхронізації можуть використовуватися самі процеси і потоки (коли один потік чекає завершення іншого потоку або процесу); а також файли, комунікаційні пристрої, консольне введення і повідомлення про зміну [2].

Будь-який об'єкт синхронізації може перебувати в так званому сигнальному стані. Для кожного типу об'єктів цей стан має різний зміст. Потоки можуть перевіряти поточний стан об'єкта та / або чекати зміни цього стану і таким чином узгоджувати свої дії. При цьому гарантується, що коли потік працює з об'єктами синхронізації (створює їх, змінює стан) система не перерве його виконання, поки він не завершить цю дію.

      1. М'ютекс


М’ютекс є взаємовиключним синхронізуючим об’єктом. Це означає, що він може бути отриманий потоком тільки по черзі. М'ютекс призначений для тих ситуацій, в яких загальний ресурс може бути одночасно використаний тільки в одному потоці. Припустимо, що системний журнал спільно використовується в декількох процесах, але тільки в одному з них дані можуть записуватися в файл цього журналу в будь-який момент часу. Для синхронізації процесів в даній ситуації ідеально підходить м’ютекс [2].


М'ютекс підтримується в класі System.Threading.Mutex. У нього є кілька конструкторів. Нижче наведені два найбільш уживаних конструктора.

public Mutex()

public Mutex (bool initiallyOwned)

У першій формі конструктора створюється м'ютекс, яким спочатку ніхто не володіє. А в другій формі вихідним станом мьютекса заволодіває потік, що викликає, якщо параметр initiallyOwned має логічне значення true. В іншому випадку м'ютексом ніхто не володіє.

Для того щоб отримати м'ютекс, в коді програми слід викликати метод WaitOne() для цього мьютекса. Метод WaitOne() успадковується класом Mutex від класу Thread.WaitHandle. Нижче наведена його найпростіша форма.

public bool WaitOne();

Метод WaitOne() очікує до того часу, поки не буде отримано м'ютекс, для якого він був викликаний. Отже, цей метод блокує виконання потоку, що викликає до того часу, поки не стане доступним вказаний м'ютекс. Він завжди повертає логічне значення true.

Коли ж в коді більше не потрібно володіти м'ютексом, він звільняється за допомогою виклику методу ReleaseMutex(), форма якого наведена нижче.

public void ReleaseMutex()

У цій формі метод ReleaseMutex() звільняє мьютекс, для якого він був викликаний, що дає можливість іншому потоку отримати даний м'ютекс.

Для застосування мьютекса з метою синхронізувати доступ до загального ресурсу згадані вище методи WaitOne() і ReleaseMutex() використовуються так, як показано в наведеному нижче фрагменті коду.

Mutex myMtx = new Mutex();

// ...

myMtx.WaitOne(); // очікувати отримання мьютекса

// Отримати доступ до загального ресурсу.

myMtx.ReleaseMutex(); // звільнити м'ютекс

При виклику методу WaitOne() виконання відповідного потоку призупиняється до того часу, поки не буде отримано м'ютекс. А при виклику методу ReleaseMutex() м'ютекс звільняється, й потім може бути отриманий іншим потоком. Завдяки такому підходу до синхронізації одночасний доступ до загального ресурсу обмежується тільки одним потоком.

      1. Семафор


Семафор подібний м'ютексу, за винятком того, що він надає одночасний доступ до загального ресурсу не одному, а кільком потокам. Тому семафор придатний для синхронізації цілого ряду ресурсів. Семафор управляє доступом до загального ресурсу, використовуючи для цієї мети лічильник. Якщо значення лічильника більше нуля, то доступ до ресурсу дозволений. А якщо це значення дорівнює нулю, то доступ до ресурсу заборонено. За допомогою лічильника ведеться підрахунок кількості дозволів. Отже, для доступу до ресурсу потік повинен отримати дозвіл від семафора [2].

Зазвичай потік, якому потрібен доступ до загального ресурсу, намагається отримати дозвіл від семафора. Якщо значення лічильника семафора більше нуля, то потік отримує дозвіл, а лічильник семафора декрементується. В іншому випадку потік блокується до того часу, поки не отримає дозвіл. Коли ж потоку більше не потрібен доступ до загального ресурсу, він вивільняє дозвіл, а лічильник семафора інкрементирується. Якщо дозволу чекає інший потік, то він отримує його в цей момент. Кількість одночасно дозволених доступів вказується при створенні семафора. Так, якщо створити семафор, який одночасно дозволяє тільки один доступ, то такий семафор буде діяти як м'ютекс.

Семафори особливо корисні в тих випадках, коли загальний ресурс складається з групи або пулу ресурсів. Наприклад, пул ресурсів може складатися з цілого ряду мережевих з'єднань, кожне з яких служить для передачі даних. Потоку, якому потрібно мережеве з'єднання, все одно, яке саме з'єднання він отримає. В даному випадку семафор забезпечує зручний механізм управління доступом до мережевих з'єднань [2].

Семафор реалізується в класі System.Threading.Semaphore, у якого є декілька конструкторів. Нижче наведена найпростіша форма конструктора даного класу:

public Semaphore(int initialCount, int maximumCount)

де initialCount - це початкове значення для лічильника дозволів семафора, тобто кількість доступних дозволів;

maximumCount - максимальне значення даного лічильника, тобто максимальна кількість дозволів, які може дати семафор.

Семафор застосовується таким же чином, як і описаний раніше м'ютекс. З метою отримання доступу до ресурсу в коді програми викликається метод WaitOne() для семафора. Цей метод успадковується класом Semaphore від класу WaitHandle. Метод WaitOne() очікує до того часу, поки не буде отримано семафор, для якого він викликається. Таким чином, він блокує виконання потоку, що викликає до того часу, поки вказаний семафор не надасть дозвіл на доступ до ресурсу [1].

Якщо коду більше не потрібно володіти семафором, він звільняє його, викликаючи метод Release(). Нижче наведено дві форми цього методу:

public int Release()

public int Release(int releaseCount)

У першій формі метод Release() вивільняє тільки один дозвіл, а в другій формі - кількість дозволів, які визначаються параметром releaseCount. В обох формах даний метод повертає підраховану кількість дозволів, які існували до вивільнення.

В .NET пропонується два класи з функціональністю семафора:

Semaphore і

SemaphoreSlim.

Клас Semaphore може бути іменований, використовувати ресурси в масштабі всієї системи і забезпечувати синхронізацію між різними процесами. Клас SemaphoreSlim є полегшеною версією класу Semaphore, яка оптимізована для забезпечення більш короткого часу очікування.

      1. Події


Події є ще один ресурс для забезпечення синхронізації в масштабі всієї системи.

Для використання системних подій з керованого коду, .NET Framework пропонує класи ManualResetEvent, AutoResetEvent, ManualResetEventSlim і CountdownEvent, які знаходяться в просторі імен System.Threading. Класи ManualResetEventSlim і CountdownEvent з'явилися у версії .NET 4 [2].

Ці класи є похідними від класу EventWaitHandle, що знаходиться на верхньому рівні ієрархії класів, і застосовуються в тих випадках, коли один потік чекає появи деякої події в іншому потоці. Як тільки така подія з'являється, другий потік повідомляє про нього перший потік, дозволяючи тим самим відновити його виконання.

Нижче наведені конструктори класів ManualResetEvent і AutoResetEvent:

public ManualResetEvent(bool initialState)

public AutoResetEvent(bool initialState)

Якщо в обох формах параметр initialState має логічне значення true, то про подію спочатку повідомляється. А якщо він має логічне значення false, то про подію спочатку не буде повідомлено.

Для події типу ManualResetEvent порядок застосування наступний. Потік, що очікує деяку подію, викликає метод WaitOne() для подієвого об'єкта, що представляє дану подію. Якщо подієвий об'єкт знаходиться в сигнальному стані, то відбувається негайне повернення з методу WaitOne(). В іншому випадку виконання потоку, що викликає призупиняється до того часу, поки не буде отримано повідомлення про подію. Як тільки подія відбудеться в іншому потоці, цей потік встановить подієвий об'єкт в сигнальний стан, викликавши метод Set(). Тому метод Set() слід розглядати як повідомляє про те, що подія відбулася.

Після установки подієвого об'єкта в сигнальний стан станеться негайне повернення з методу WaitOne(), і перший потік відновить своє виконання. А в результаті виклику методу Reset() подієвий об'єкт повертається в несигнальному стан.

Подія AutoResetEvent відрізняється від події типу ManualResetEvent лише способом установки в початковий стан. Якщо, для події типу ManualResetEvent подієвий об'єкт залишається в сигнальному стані доти, поки не буде викликаний метод Reset(), то для події типу AutoResetEvent подієвий об'єкт автоматично переходить в несигнальному стан, як тільки потік, який чекає цю подію, отримає повідомлення про нього і відновить своє виконання. Тому якщо застосовується подія типу AutoResetEvent, то викликати метод Reset() необов'язково [2].

Подія ManualResetEventSlim перекладається в сигнальний стан викликом методу Set(), а за допомогою Reset() повертається назад в несигнальному стан. У разі виклику методу Set() при наявності безлічі потоків, які чекають переходу події в сигнальний стан, очікування всіх цих потоків негайно припиняється. У разі, якщо потік просто викликає метод WaitOne(), а подія вже знаходиться в сигнальному стані, потік, який чекав, може відразу ж продовжити роботу.

      1. Клас Monitor і блокування


Ключове слово lock служить в C# швидким способом доступу до засобу синхронізації, визначеним у класі Monitor, який знаходиться в просторі імен System.Threading. В цьому класі визначено, зокрема, ряд методів для управління синхронізацією. Наприклад, для отримання блокування об'єкта викликається метод Enter(), а для зняття блокування - метод Exit(). Нижче наведені загальні форми цих методів:

public static void Enter (object obj)

public static void Exit (object obj)

де obj позначає синхронізований об'єкт. Якщо ж об'єкт недоступний, то після виклику методу Enter() потік, що викликає, очікує до того часу, поки об'єкт не стане доступним. Проте методи Enter() і Exit() застосовуються рідко, оскільки оператор lock автоматично надає еквівалентні засоби синхронізації потоків. Саме тому оператор lock виявляється "кращим" для отримання блокування об'єкта при програмуванні на С# [2].

Втім, один метод з класу Monitor може все ж виявитися корисним. Це метод TryEnter(), одна із загальних форм якого наведена нижче.

public static bool TryEnter (object obj)

Цей метод повертає логічне значення true, якщо потік, що викликає, отримує блокування для об'єкта obj, а інакше він повертає логічне значення false.

Але в будь-якому випадку потоку, що викликає, доведеться чекати своєї черги. За допомогою методу TryEnter() можна реалізувати альтернативний варіант синхронізації потоків, якщо необхідний об'єкт тимчасово недоступний.

      1. Методи Wait(), Pulse() і PulseAll()


Методи Wait(), Pulse() і PulseAll() визначені в класі Monitor і можуть викликатися тільки з заблокованого фрагмента блоку. Вони застосовуються таким чином.

Коли виконання потоку тимчасово заблоковано, він викликає метод Wait(). В результаті потік переходить в стан очікування, а блокування з відповідного об'єкта знімається, що дає можливість використовувати цей об'єкт в іншому потоці. Надалі потік, що очікує, активізується, коли інший потік увійде в аналогічний стан блокування, і викликає метод Pulse() або PulseAll(). При виклику методу Pulse() поновлюється виконання першого потоку, що очікує своєї черзі на отримання блокування. А виклик методу PulseAll() сигналізує про зняття блокування всім потокам, що очікують [2].

Нижче наведено дві найбільш часто використовувані форми методу Wait().

public static bool Wait (object obj)

public static bool Wait (object obj, int міллісекунд_простоя)

У першій формі очікування триває аж до повідомлення про звільнення об'єкта, а в другій формі - як до повідомлення про звільнення об'єкта, так і до закінчення періоду часу, на який вказує кількість міллісекунд_простоя. В обох формах obj позначає об'єкт, звільнення якого очікується.

Нижче наведені загальні форми методів Pulse() і PulseAll():

public static void Pulse (object obj)

public static void PulseAll (object obj)

де obj позначає звільняється об'єкт.

Якщо методи Wait(), Pulse() і PulseAll() викликаються з коду, що знаходиться за межами синхронізованого коду, наприклад з блоку lock, то генерується виняток SynchronizationLockException.

      1. Атрибут MethodImplAttribute


Метод може бути повністю синхронізований з допомогою атрибута MethodImplAttribute. Такий підхід може стати альтернативою оператору lock в тих випадках, коли метод потрібно заблокувати повністю. Атрибут MethodImplAttribute визначено в просторі імен Sуstem.Runtime. CompilerServices. Нижче наведено конструктор, застосовуваний для подібної синхронізації:

public MethodImplAttribute (MethodImplOptions methodImplOptions)

де methodImplOptions позначає атрибут реалізації. Для синхронізації методу досить вказати атрибут MethodImplOptions.Synchronized. Цей атрибут викликає блокування всього методу для поточного екземпляра об'єкта, доступного за посиланням this. Якщо ж метод відноситься до типу static, то блокується його тип. Тому даний атрибут непридатний для застосування у відкритих об'єктах або класах.

      1. Клас Interlocked


Цей клас є в якості альтернативи іншим засобам синхронізації, коли потрібно тільки змінити значення загальної змінної. Методи, доступні в класі Interlocked, гарантують, що їх дія буде виконуватися як єдина, неперервні операція. Це означає, що ніякої синхронізації в даному випадку взагалі не вимагається. У класі Interlocked надаються статичні методи для складання двох цілих значень, инкрементирования і декрементірованія цілого значення, порівняння і установки значень об'єкта, обміну об'єктами і отримання 64-разрядного значення. Всі ці операції виконуються без переривання.


    1. 1   2   3   4   5   6   7   8   9   10

      скачати

© Усі права захищені
написати до нас