У меня есть объект, который предлагает клиентам Subscribe(Observer*)
и Unsubscribe(Observer*)
. Тема запускается в своем собственном потоке (из которого он вызывает Notify()
для подписанных наблюдателей), а мьютекс защищает свой внутренний список наблюдателей.
Я бы хотел, чтобы клиентский код, который я не контролирую, чтобы безопасно удалить Observer после его отмены. Как это можно достичь?
- Удержание мьютекса - даже рекурсивное мьютекс - пока я уведомляю наблюдателей не является вариантом из-за риск взаимоблокировки.
- Я мог бы отметить наблюдателя для удаления в вызове Unsubscribe и удалите его из темы Тема. затем клиенты могут ждать специального Уведомление "Безопасное удаление". Эта выглядит безопасно, но обременительно для клиенты.
Edit
Ниже приведен пример иллюстративного кода. Проблема заключается в том, как предотвратить Unsubscribe, когда Run находится в комментарии "Проблема здесь". Затем я мог бы вернуться к удаленному объекту. В качестве альтернативы, если я держу мьютекс повсюду, а не копирую, я могу затормозить определенных клиентов.
#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;
};
Изменить
Я не могу уведомить наблюдателей, удерживая мьютекс из-за риска взаимоблокировки. Самый очевидный способ, которым это может случиться, - вызов клиента Подписаться или Отменить подписку изнутри Notify - легко исправить, сделав мьютекс рекурсивным. Более коварным является риск прерывистого тупика на разных потоках.
Я работаю в многопоточной среде, поэтому в любой момент выполнения потока он обычно будет содержать последовательность блокировок L1, L2,... Ln. В другой нити будут зафиксированы замки K1, K2,... Km. Правильно написанный клиент гарантирует, что разные потоки будут всегда получать блокировки в том же порядке. Но когда клиенты взаимодействуют с моим мьютексом Subject - назовите его X - эта стратегия будет нарушена: вызовы для подписки/отмены подписки получают блокировки в порядке L1, L2,... Ln, X. Звонки на уведомление из моей темы темы получают блокировки в порядок X, K1, K2,... Km. Если какой-либо из Li или Kj может совпадать по любому пути вызова, клиент страдает прерывистым тупиком, с небольшой перспективой отладки его. Поскольку я не контролирую клиентский код, я не могу этого сделать.