Реалізація задачі виробників-споживачів за допомогою м’ютексів



З’ясуємо можливість розв’язання задачі ввиробників-споживачів за допомогою самих лише м’ютексів та розглянемо проблеми, які з цим пов’язані. Тут не наводитимемо повного коду розв’язання завдання, зупинимося тільки на ключових моментах.

Проблем із реалізацією критичних секцій не виникне, оскільки в цьому разі м’ютекси використовуватимуться за прямим призначенням.

Розглянемо організацію очікування виконання умови. Ця задача досить складна, оскільки м’ютекс, з одного боку, має тільки два стани, а з іншого – не може бути зайнятий в одному потоці, а звільнений в іншому. Це змушує вдатися до активного очікування, перевіряючи в циклі, чи не стала умова істинною (при цьому займаючи м’ютекс на час перевірки і звільняючи після неї). Ось як можна реалізувати функцію consumer():

mutex_t lock;

void consumer() {

item_t item;

for (; ;) {                                                              //нескінчений цикл активного очікування

//вихід із нього, якщо буфер не порожній

mutex_lock(lock);                                         //вхід у критичну секцію

if (count_items_in_buffer() != 0) {              //якщо буфер не порожній

item = receive_from_buffer();        //забрати об’єкт із буфера

mutex_unlock(lock);                          //вийти із критичної секції

break;                                              //вийти з циклу

}

mutex_unlock(lock);                                      //вихід із критичної секції

}

consume(item);                                                    //спожити об’єкт

}

Реалізація функції producer() загалом аналогічна. Очевидно, що м’ютекси не підходять для організації умовного очікування. Потрібен примітив, який дасть можливість потоку переходити у стан очікування до настання деякої події. Таким примітивом є умовна змінна.

Рекурсивні м’ютекси

Розглянемо випадок, коли потік, який займає м’ютекс, спробує зайняти той самий м’ютекс іще раз. Для звичайних м’ютексів отримаємо взаємне блокування, оскільки потік очікуватиме на м’ютексі, що його звільнити може тільки він сам.

Рекурсивний м’ютекс – особливий вид м’ютекса. Він дозволяє повторне зайняття тим самим потоком, а також відстежує, який потік намагається його зайняти. Коли це не той потік, що його вже займає, м’ютекс поводиться як звичайний, і потік переходить у стан очікування. Коли ж це той самий потік, внутрішній лічильник блокувань цього м’ютекса збільшують на одиницю, і потік продовжує своє виконання. У разі звільнення м’ютекса внутрішній лічильник зменшують на одиницю, для інших потоків рекурсивний м’ютекс буде розблоковано тільки тоді, коли лічильник дійде до нуля (тобто коли всі його блокування одним потоком будуть зняті).

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

Умовні змінні та концепція монітора

Поняття умовної змінної

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

· Очікування (wait). Додатковим вхідним параметром ця операція приймає м’ютекс, який повинен перебувати в закритому стані. Виклик wait відбувається в ситуації, коли не виконується деяка умова, потрібна потоку для продовження роботи. Внаслідок виконання wait потік (позначатимемо його TW) припиняється (кажуть, що він «очікує на умовній змінній»), а м’ютекс відкривається (ці дві дії виконуються атомарно). Так інші потоки отримують можливість увійти в критичну секцію і змінити там дані, які вона захищає, можливо, виконавши умову, потрібну потоку TW. На цьому операція wait не завершується – її завершить інший потік, викликавши операцію signal після того, як умову буде виконано.

· Сигналізація (signal). Цю операцію потік (назвемо його TS) має виконати після того, як увійде у критичну секцію і завершить роботу з даними (виконавши умову, яку очікував потік, що викликав операцію wait). Ця операція перевіряє, чи немає потоків, які очікують на умовній змінній, і якщо такі потоки є, переводить один із них (TW) у стан готовності (цей потік буде поновлено, коли відповідний потік TS вийде із критичної секції). Внаслідок поновлення потік TW завершує виконання операції wait – блокує м’ютекс знову (поновлення і блокування теж відбуваються атомарно). Якщо немає жодного потоку, який очікує на умовній змінній, операція signal не робить нічого, і інформація про її виконання в системі не зберігають.

· Широкомовна сигналізація (broadcast) відрізняється від звичайної тим, що переведення у стан готовності і, зрештою, поновлення виконують для всіх потоків, які очікують на цій умовній змінній, а не тільки для одного з них.

Отже, виконання операції wait складається з таких етапів: відкриття м’ютекса, очікування (поки інший потік не виконає операцію signal або broadcast), закриття м’ютекса.

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


Дата добавления: 2018-04-05; просмотров: 426; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!