Семафори в pthreads

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

pthreads семафор - це змінна типу sem_t , яка може перебувати в заданому числі станів. Кожен потік може збільшити лічильник семафора або зменшити його. У літературі операція збільшення значення лічильника називається V (від датського verhogen – збільшувати, або врійгаве – звільняти). Для простоти можна запам'ятовувати як англійський vacate (звільняти). Зменшення лічильника – це операція P (proberen – тестувати, чи passeren – проходити, чи pakken – захоплювати, і навіть probeer te verlagen – спробувати зменшити), чи англійською procure – добувати.

Зауваження: іноді м'ютекс називають двійковим семафором, вказуючи не те, що м'ютекс може перебувати у двох станах. Але тут є важлива відмінність: розблокувати м'ютекс може лише той потік, який його заблокував, а семафор може «розблокувати» будь-який потік.

Семафори описані у бібліотеці semaphore.h. Робота з семаформ і схожа на роботу з м'ютексами. Спочатку необхідно ініціалізувати семафор за допомогою функції

де sem - це покажчик на семафор, pshared - прапор, що вказує, чи семафор повинен бути розшарований при використанні функції fork() , value - початкове значення семафору.

Далі, для очікування доступу використовується функція

Якщо значення семафора негативне, то викликаючий потік блокується доти, доки один із потоків не викличе sem_post

Ця функція збільшує значення семафору і розблокує потоки очікування. Як завжди, наприкінці необхідно знищитисемафор

Крім того, можна отримати поточне значення семаформу

Тут змінну valp буде поміщено значення лічильника.

Звернемося знову до старого прикладу. Два потоки змінюють одну змінну, копіюючи їх у локальну змінну. Один потік збільшує значення, а другий зменшує 100 разів. Таким чином, наприкінці має бути 0. Через спільний доступ до змінної отримуємо щоразу різне значення

Синхронізуємо тепер їх за допомогою семафору. Розглянемо спочатку приклад, коли лічильник дорівнює 1, значить, після того, як один із потоків зменшить значення семафору, то другий змушений буде чекати доступу до ресурсу (в даному випадку, це тіло функції worker1 або worker2)

Тут все зрозуміло. Збільшимо тепер значення лічильника до 3, наприклад. Замінимо

Тепер ми отримуємо ту ж плутанину. Наприкінці counter навряд чи дорівнюватиме нулю. Це відбувається від того, що після зменшення значення семафора він ще не дорівнює нулю (3 - 1 = 2), через що блокування не відбувається. Оскільки немає блокування, ще два потоки можуть змінити пермінну.

М'ютекси та семафори

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

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

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

М'ютекс://Потік 1 захопити_м'ютекс(ключи_від_вбиральні) використовувати_ресурс віддати_м'ютекс(ключи_від_вбиральні)

//Потік 2 захопити_м'ютекс(ключи_від_вбиральні) використовувати_ресурс віддати_м'ютекс(ключи_від_вбиральні)

Семафор//Потік 1 послати_сигнал(стан_кнопки)

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

Розглянемо наступний приклад. Є три потоки. Усі три створюються одночасно, але другий повинен працювати лише після того, як відпрацює перший, а третій після того, як відпрацює другий.