Добустим из одного потока вызывается method субъекта, который
вызывает метод update обсервера, в результате его выполнения
обсервер решает, что больше он не нужен и вызывает метод release
где призводит все необходимые операции и отписывается от субъекта.
В это время из другого потока, также происходит вызов метода
release() обсервера. Если этот вызов происходит после вызова
update(), но до вызова release() из update(), то на методе
erase() возникает деадлок. Интересует, как эту проблему можно
элегантно решить?
Это вообще большая засада, когда у каждого объекта свой мьютекс, и поток может войти в две критические секции сразу (сперва в одну, затем, не выходя из неё, в другую).
Дедлоки неизбежны.
Выходов 4:
Алгоритм банкира и другие способы глобально следить за порядком захвата мьютексов.
Отказаться от локальных мьютексов и сделать апартаменты (самая примитивная реализация — просто глобальный мьютекс).
Поток заходит в апартаменты и делает там всё, что хочет; в это время другой поток ждёт у дверей.
Подробности такой модели — читай в книжках по COM, там эта техника используется вовсю.
Сделать "локальные апартаменты" у каждого объекта. Выход за границу объекта (вызов стороннего метода) приводит к выходу из апартаментов.
class subject : public isubject
{
mutex m_;
iobserver* observer_;
void method()
{
mutex::scoped_lock lk(m_);
// в апартаментах
.....
// здесь объект находится в согласованном состоянии,
// и мы безопасно выходим...
lk.unlock();
observer_->update();
lk.lock();
// снова в апартаментах
.....
// вышли из блока - вышли из апартаментов
}
};
// Аналогично - observer
Эта техника хорошо известна при работе с conditional variable — только там сама cv обеспечивает выход-вход.
Отказаться от синхронных вызовов, заменив их на посылку асинхронных сообщений.
Пусть observer живёт в своём потоке, а subject'ы посылают туда сигналы обновиться и отписаться. (Естественно, отписка более приоритетна, чем обновление и даже, возможно будет синхронной).
Способ посылки сигнала — в ассортименте. Начиная с атомарного булева флажка и кончая очередью.
Здравствуйте, Кодт, Вы писали:
К> Алгоритм банкира и другие способы глобально следить за порядком захвата мьютексов.
Что-то он мне показался чересчур ресурсоемким, но еще не смотрел подробно
К> Отказаться от локальных мьютексов и сделать апартаменты (самая примитивная реализация — просто глобальный мьютекс). К>Поток заходит в апартаменты и делает там всё, что хочет; в это время другой поток ждёт у дверей. К>Подробности такой модели — читай в книжках по COM, там эта техника используется вовсю.
Насколько я помню COM один объект может принадлежать только одному апартаменту, т.е. для subject и всех его observer,
должен быть один мутекс. Но если observer наблюдает еще за другим объектом, то его тоже надо в ключать в этот апартамент и т.д.
что не хотелось бы, т.к. обсерверы разнесены по потокам т.к. в моем проекте достаточно ресурсоемки
К> Сделать "локальные апартаменты" у каждого объекта. Выход за границу объекта (вызов стороннего метода) приводит к выходу из апартаментов. К>
К>class subject : public isubject
К>{
К> mutex m_;
К> iobserver* observer_;
К> void method()
К> {
К> mutex::scoped_lock lk(m_);
К> // в апартаментах
К> .....
К> // здесь объект находится в согласованном состоянии,
К> // и мы безопасно выходим...
К> lk.unlock();
К> observer_->update();
К> lk.lock();
К> // снова в апартаментах
К> .....
К> // вышли из блока - вышли из апартаментов
К> }
К>};
К>// Аналогично - observer
К>
К>Эта техника хорошо известна при работе с conditional variable — только там сама cv обеспечивает выход-вход.
Такой подход применим для локальных вызовов при нерекурсиавных мутексах. В данном примере, обсервер
может отписаться из другого потока после lk.unlock(); и до observer_->update();, что приведет к ошибке.
К> Отказаться от синхронных вызовов, заменив их на посылку асинхронных сообщений. К>Пусть observer живёт в своём потоке, а subject'ы посылают туда сигналы обновиться и отписаться. (Естественно, отписка более приоритетна, чем обновление и даже, возможно будет синхронной). К>Способ посылки сигнала — в ассортименте. Начиная с атомарного булева флажка и кончая очередью.
В принципе у меня так и сделанно (через очередь), но мне не нравиться что много кода работы с очередью, хотя классы по сути простые.
В общем большое спасибо, Кодт, за ответ, подумаю над апартаментами или оставлю последний вариант, жаль что простого решения нет.
Здравствуйте, s_viy, Вы писали:
К>> Алгоритм банкира и другие способы глобально следить за порядком захвата мьютексов. _>Что-то он мне показался чересчур ресурсоемким, но еще не смотрел подробно
Естественно. Я его упомянул так, для общего развития.
К>> Отказаться от локальных мьютексов и сделать апартаменты (самая примитивная реализация — просто глобальный мьютекс). К>>Поток заходит в апартаменты и делает там всё, что хочет; в это время другой поток ждёт у дверей. К>>Подробности такой модели — читай в книжках по COM, там эта техника используется вовсю. _>Насколько я помню COM один объект может принадлежать только одному апартаменту, т.е. для subject и всех его observer, _>должен быть один мутекс. Но если observer наблюдает еще за другим объектом, то его тоже надо в ключать в этот апартамент и т.д. _>что не хотелось бы, т.к. обсерверы разнесены по потокам т.к. в моем проекте достаточно ресурсоемки
Каждый объект принадлежит ровно одному апартаменту, это правда.
Но это не значит, что всё семейство должно принадлежать одному и тому же.
Просто обращения к объектам из других апартаментов выполняются специальным образом
К>> Сделать "локальные апартаменты" у каждого объекта. Выход за границу объекта (вызов стороннего метода) приводит к выходу из апартаментов. К>>Эта техника хорошо известна при работе с conditional variable — только там сама cv обеспечивает выход-вход. _>Такой подход применим для локальных вызовов при нерекурсиавных мутексах. В данном примере, обсервер _>может отписаться из другого потока после lk.unlock(); и до observer_->update();, что приведет к ошибке.
Я специально оговорил, что перед выходом из апартамента объект должен быть в согласованном состоянии. В частности, это значит, что у него можно вызвать любые методы.
Если твой метод принципиально не реентерабельный, то конечно, никакие апартаменты (ни COM-овские, ни cv-шные) не спасут.
Мьютекс — это очень низкоуровневая и примитивная защита от реентера. Тут нужно мыслить ширше и хлубже, чтоб увидеть проблему и решение не на уровне гонок или нарушения совместного доступа...
К>> Отказаться от синхронных вызовов, заменив их на посылку асинхронных сообщений. К>>Пусть observer живёт в своём потоке, а subject'ы посылают туда сигналы обновиться и отписаться. (Естественно, отписка более приоритетна, чем обновление и даже, возможно будет синхронной). К>>Способ посылки сигнала — в ассортименте. Начиная с атомарного булева флажка и кончая очередью. _>В принципе у меня так и сделанно (через очередь), но мне не нравиться что много кода работы с очередью, хотя классы по сути простые.
_>В общем большое спасибо, Кодт, за ответ, подумаю над апартаментами или оставлю последний вариант, жаль что простого решения нет.
Дык, это архитектурная проблема.
А что касается работы с очередью — попробуй отделить механику очередей от бизнес-логики.
Ещё один вариант — переделать всё на conditional variable.