Застосування мьютекса і семафора

Застосування мьютекса і семафора

Книга розрахована на широке коло читачів, які цікавляться програмуванням на C # .Введіте сюди коротку анотацію

Книга: C # 4.0: повне керівництво

Застосування мьютекса і семафора

Розділи на цій сторінці:

Застосування мьютекса і семафора

У більшості випадків, коли потрібна синхронізація, виявляється досить і оператора lock. Проте в деяких випадках, як, наприклад, при обмеженні доступу до загальних ресурсів, більш зручними виявляються механізми синхронізації, вбудовані в середу .NET Framework. Нижче розглядаються по порядку два таких механізму: м'ютекс і семафор.

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

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

public Mutex ()
public Mutex (bool initiallyOwned)

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

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

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

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

public void ReleaseMutex ()

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

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

Mutex myMtx = new Mutex ();
//.
myMtx.WaitOne (); // очікувати отримання мьютекса
// Отримати доступ до загального ресурсу.
myMtx.ReleaseMutex (); // звільнити м'ютекс

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

У наведеному нижче прикладі програми описаний вище механізм синхронізації демонструється на практиці. У цій програмі створюються два потоки у вигляді класів IncThread і DecThread. яким потрібен доступ до загального ресурсу: змінної SharedRes.Count. У потоці IncThread змінна SharedRes.Count инкрементируется, а в потоці DecThread - декрементируется. Щоб уникнути одночасного доступу обох потоків до загального ресурсу SharedRes.Count цей доступ синхронізується м'ютексів Mtx. також є членом класу SharedRes.

Ця програма дає наступний результат.

Инкрементируются Потік очікує м'ютекс.
Инкрементируются Потік отримує м'ютекс.
Декрементируется Потік очікує м'ютекс.
У потоці инкрементируются Потік, SharedRes.Count = 1
У потоці инкрементируются Потік, SharedRes.Count = 2
У потоці инкрементируются Потік, SharedRes.Count = 3
У потоці инкрементируются Потік, SharedRes.Count = 4
У потоці инкрементируются Потік, SharedRes.Count = 5
Инкрементируются Потік звільняє мьютекс.
Декрементируется Потік отримує м'ютекс.
У потоці декрементируется Потік, SharedRes.Count = 4
У потоці декрементируется Потік, SharedRes.Count = 3
У потоці декрементируется Потік, SharedRes.Count = 2
У потоці декрементируется Потік, SharedRes.Count = 1
У потоці декрементируется Потік, SharedRes.Count = 0
Декрементируется Потік звільняє мьютекс.

Як випливає з наведеного вище результату, доступ до загального ресурсу (змінної SharedRes.Count) синхронізований, і тому значення даної змінної може бути одночасно змінено тільки в одному потоці.

Инкрементируются Потік очікує м'ютекс.
Инкрементируются Потік отримує м'ютекс.
Декрементируется Потік очікує м'ютекс.
Декрементируется Потік отримує м'ютекс.
У потоці инкрементируются Потік, SharedRes.Count = 1
У потоці декрементируется Потік, SharedRes.Count = 0
У потоці инкрементируются Потік, SharedRes.Count = 1
У потоці декрементируется Потік, SharedRes.Count = 0
У потоці инкрементируются Потік, SharedRes.Count = 1
У потоці декрементируется Потік, SharedRes.Count = 0
У потоці инкрементируются Потік, SharedRes.Count = 1
У потоці декрементируется Потік, SharedRes.Count = 0
У потоці инкрементируются Потік, SharedRes.Count = 1
Инкрементируются Потік звільняє мьютекс.
У потоці декрементируется Потік, SharedRes.Count = 0
Декрементируется Потік звільняє мьютекс.

Як випливає з наведеного вище результату, без мьютекса инкрементирование і декрементірованіе змінної SharedRes.Count відбувається, скоріше, безладно, ніж послідовно.

М'ютекс, створений в попередньому прикладі, відомий тільки тому процесу, який його породив. Але м'ютекс можна створити і таким чином, щоб він був відомий деінде. Для цього він повинен бути іменованих. Нижче наведені форми конструктора, призначені для створення такого мьютекса.

public Mutex (bool initiallyOwned, string ім'я)
public Mutex (bool initiallyOwned, string ім'я, out bool createdNew)

В обох формах конструктора ім'я позначає конкретне ім'я мьютекса. Якщо в першій формі конструктора параметр ini tiallyOwned має логічне значення true. то володіння м'ютексів запитується. Але оскільки м'ютекс може належати іншому процесу на системному рівні, то для цього параметра краще вказати логічне значення false. А після повернення з другої форми конструктора параметр createdNew матиме логічне значення true. якщо володіння м'ютексів, було отримано, і логічне значення false. якщо запит на володіння був відхилений. Існує і третя форма конструктора типу Mutex. в якій допускається вказувати керуючий доступом об'єкт типу MutexSecurity. За допомогою іменованих м'ютексів можна синхронізувати взаємодія процесів.

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

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

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

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

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

public Semaphore (int initialCount, int maximumCount)

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

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

Схожі статті