У меня есть субъект, который предлагает Subscribe(Observer*)
и Unsubscribe(Observer*)
клиентам. Субъект работает в своем собственном потоке (из которого он вызывает Notify()
для подписанных наблюдателей), а мьютекс защищает свой внутренний список наблюдателей.
Я бы хотел, чтобы клиентский код, который я не контролирую, мог безопасно удалить Observer после того, как он отписался. Как этого достичь?
- Удержание мьютекса - даже рекурсивный
мьютекс - пока я уведомляю наблюдателей
не вариант из-за
риск тупиковой ситуации.
- Я мог бы отметить наблюдателя для удаления
в Unsubscribe call и удалите его
из Темы темы. затем
клиенты могут ждать специального
Уведомление «Безопасно удалить». это
выглядит безопасно, но обременительно для
клиентов.
Редактировать
Ниже приведен иллюстративный код. Проблема заключается в том, как предотвратить отмену подписки, когда в разделе «Проблема здесь» находится пункт «Выполнить». Тогда я мог бы перезвонить на удаленный объект. В качестве альтернативы, если я удерживаю мьютекс, а не делаю копию, я могу заблокировать некоторые клиенты.
#include <set>
#include <functional>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
using namespace std;
using namespace boost;
class Observer
{
public:
void Notify() {}
};
class Subject
{
public:
Subject() : t(bind(&Subject::Run, this))
{
}
void Subscribe(Observer* o)
{
mutex::scoped_lock l(m);
observers.insert(o);
}
void Unsubscribe(Observer* o)
{
mutex::scoped_lock l(m);
observers.erase(o);
}
void Run()
{
for (;;)
{
WaitForSomethingInterestingToHappen();
set<Observer*> notifyList;
{
mutex::scoped_lock l(m);
notifyList = observers;
}
// Problem here
for_each(notifyList.begin(), notifyList.end(),
mem_fun(&Observer::Notify));
}
}
private:
set<Observer*> observers;
thread t;
mutex m;
};
Редактировать
Я не могу уведомить наблюдателей, удерживая мьютекс из-за риска тупика. Наиболее очевидный способ, которым это может произойти - клиент вызывает Subscribe или Unsubscribe изнутри Notify - легко исправить, сделав мьютекс рекурсивным. Более коварным является риск прерывистой блокировки в разных потоках.
Я нахожусь в многопоточной среде, поэтому в любой момент выполнения потока он обычно содержит последовательность блокировок L1, L2, ... Ln. Другая нить будет держать замки K1, K2, ... Km. Правильно написанный клиент гарантирует, что разные потоки всегда будут получать блокировки в одном и том же порядке. Но когда клиенты взаимодействуют с мьютексом моего субъекта - назовите его X - эта стратегия будет нарушена: вызовы подписки / отмены подписки получают блокировки в порядке L1, L2, ... Ln, X. вызовы Notify из моего потока темы получают блокировки порядок X, K1, K2, ... Km. Если какой-либо из Li или Kj может совпасть по какому-либо пути вызова, клиент подвергается периодической тупиковой ситуации с небольшой вероятностью отладки. Поскольку я не контролирую код клиента, я не могу этого сделать.