Лекція: Монітори та умовні змінні
Вступ
У багатозадачних середовищах виникає необхідність координувати взаємодію між процесами або потоками, які можуть одночасно звертатися до спільних ресурсів. Примітиви синхронізації, такі як семафори та м'ютекси, допомагають вирішувати цю задачу, але їх використання може бути складним і призводити до помилок, таких як взаємне блокування (deadlock) чи голодання (starvation). Щоб зробити управління синхронізацією зручнішим і безпечнішим, використовуються монітори та умовні змінні.
Мета цієї лекції — ознайомити вас із поняттям моніторів та умовних змінних, їхньою роллю у синхронізації та практичним застосуванням для розв'язання задач взаємодії між потоками.
1. Що таке монітор?
1.1. Визначення монітора
Монітор — це високорівневий примітив синхронізації, що дозволяє автоматично керувати доступом до критичних секцій. Він поєднує в собі м'ютекс та умовні змінні, що дозволяє потокам безпечно взаємодіяти, використовуючи вбудовані механізми блокування та розблокування.
Монітор забезпечує, що лише один потік може виконувати код всередині монітора у будь-який момент часу, що усуває необхідність у використанні зовнішніх м'ютексів.
1.2. Структура монітора
Монітор включає:
Критичні секції: методи, до яких дозволяється одночасний доступ тільки одному потоку.
Умовні змінні: дозволяють потокам чекати певних умов для продовження виконання.
2. Умовні змінні
2.1. Визначення
Умовна змінна — це спеціальний механізм у моніторах, що дозволяє потокам чекати настання певної умови, перш ніж продовжити виконання. Потоки можуть очікувати (wait) на умовній змінній або сигналізувати (signal) іншим потокам про те, що умова змінилася.
2.2. Основні операції з умовними змінними
wait() — потік, що викликає цю функцію, переходить у стан очікування, звільняючи монітор. Коли інший потік викликає signal(), очікуючий потік пробуджується та знову захоплює монітор.
signal() — пробуджує один із потоків, що чекає на умовній змінній.
broadcast() — пробуджує всі потоки, що очікують на умовній змінній.
2.3. Приклад використання умовних змінних (псевдокод)
class Monitor:
def __init__(self):
self.condition = Condition()
self.shared_resource = 0
def producer(self):
with self.condition:
while self.shared_resource > 0:
self.condition.wait() # Чекати, поки ресурс буде спожитий
self.shared_resource += 1
print("Produced 1 item")
self.condition.signal() # Сигналізувати споживачу
def consumer(self):
with self.condition:
while self.shared_resource == 0:
self.condition.wait() # Чекати, поки з'явиться ресурс
self.shared_resource -= 1
print("Consumed 1 item")
self.condition.signal() # Сигналізувати виробникуУ прикладі вище ми використовуємо умовні змінні для синхронізації між виробником та споживачем.
3. Порівняння моніторів з іншими примітивами
Характеристика | Монітор | М'ютекс | Семафор |
|---|---|---|---|
Рівень абстракції | Високий | Середній | Низький |
Автоматичне блокування | Так | Ні | Ні |
Підтримка умовних змінних | Так | Ні | Ні |
Запобігання deadlock | Частково | Ні | Ні |
Монітори забезпечують більш безпечне та зрозуміле управління синхронізацією порівняно з м'ютексами та семафорами.
4. Приклад реалізації монітора на C++ (псевдокод)
#include <iostream> #include <condition_variable> #include <mutex> #include <thread> class Monitor { private: std::mutex mtx; std::condition_variable cond; int data = 0; public: void produce() { std::unique_lock<std::mutex> lock(mtx); while (data > 0) { cond.wait(lock); } data++; std::cout << "Produced data\n"; cond.notify_one(); } void consume() { std::unique_lock<std::mutex> lock(mtx); while (data == 0) { cond.wait(lock); } data--; std::cout << "Consumed data\n"; cond.notify_one(); } }; int main() { Monitor monitor; std::thread t1(&Monitor::produce, &monitor); std::thread t2(&Monitor::consume, &monitor); t1.join(); t2.join(); return 0; }
5. Проблеми при використанні умовних змінних
Спуріозне пробудження (Spurious Wakeups): потік може бути пробуджений без виклику signal(). Тому необхідно завжди використовувати цикл while, а не if, при перевірці умов.
Голодання (Starvation): потоки з низьким пріоритетом можуть тривалий час не отримувати доступ до монітора.
Взаємне блокування (Deadlock): можливе у складних сценаріях, якщо не правильно використовувати блокування.
6. Переваги та недоліки моніторів
Переваги
Спрощують синхронізацію.
Забезпечують безпеку при одночасному доступі до даних.
Легші у використанні порівняно з низькорівневими примітивами.
Недоліки
Можуть бути менш ефективними через автоматичне блокування.
Можливі проблеми зі спуріозними пробудженнями.
Не завжди підтримуються всіма мовами програмування.
Висновок
Монітори та умовні змінні є важливими інструментами для організації синхронізації в багатозадачних програмах. Вони забезпечують зручніший та безпечніший спосіб управління доступом до спільних ресурсів у порівнянні з м'ютексами та семафорами. Однак їх використання вимагає розуміння принципів роботи багатопотокових систем, щоб уникнути потенційних проблем, таких як взаємне блокування або голодання.
Питання для вихідного контролю
Що таке монітор і як він працює?
Які основні операції виконуються з умовними змінними?
Чим відрізняються м'ютекси та монітори?
Наведіть приклад ситуації, де доречно використовувати умовні змінні.
Що таке спуріозне пробудження і як його уникнути?
Поясніть різницю між signal() та broadcast() в умовних змінних.
Які проблеми можуть виникнути при неправильному використанні моніторів?
Які переваги мають монітори над семафорами?
Що таке голодання (starvation) і як його уникнути?
Які мови програмування підтримують монітори на рівні стандартних бібліотек?














