我在正确锁定通知中心时遇到问题 与 self 注册的观察员。具体来说,我在事件调度中遇到了已经被销毁的对象。
考虑以下结构:
- 通知中心正在向多个观察者广播事件
- 观察者在创建/删除时注册/注销自己。
- 这随时可能发生
- 事件派发可能与观察者注册在不同的线程上。
- 为了同步观察者的注册和派发,这两个功能都被锁定。
最后它应该像这个最小的例子:
#include <future>
#include <functional>
#include <chrono>
#include <thread>
#include <iostream>
#include <mutex>
#include <map>
class Event {};
class Observer;
class Center {
public:
void addObserver(Observer &o);
void removeObserver(Observer &o);
void sendEvent(Event*);
static Center& instance() {static Center center; return center;}
private:
std::map<Observer*, std::function<void(Event*)>> m_observers;
std::mutex m_mutex;
};
class Observer {
public:
virtual void onEvent(Event*) = 0;
void call(Event * event) {onEvent(event);}
Observer() {Center::instance().addObserver(*this);}
virtual ~Observer() {Center::instance().removeObserver(*this);}
};
void Center::addObserver(Observer &o) {
std::lock_guard<std::mutex> l(m_mutex);
m_observers[&o] = std::bind(&Observer::call,&o,std::placeholders::_1);
}
void Center::removeObserver(Observer &o) {
std::lock_guard<std::mutex> l(m_mutex);
m_observers.erase(&o);
}
void Center::sendEvent(Event* e) {
std::lock_guard<std::mutex> l(m_mutex);
for(auto i : m_observers)
i.second(e);
}
class testObs : public Observer {
virtual void onEvent(Event*) {std::cout << "do work" << std::endl;}
};
int main()
{
auto handle = std::async(std::launch::async, []() {
Event e;
while(true) {Center::instance().sendEvent(&e);}
});
for(;;){testObs t1,t2,t3,t4;}
handle.get();
}
这将在一段时间后遇到运行时错误(R6025 - 纯虚函数调用
)。
问题在于 Observer::~Observer()
仅在 testObs
被销毁后调用,
因此锁定为时已晚,运行事件分派(dispatch)将调用已销毁的对象。
我的问题是我是否仍然可以创建一个可靠的锁(或实现另一种彻底删除对象的方法)同时 保留大部分现有结构:
- 自动观察员注册/注销
- 在通知中心内没有观察员所有权
最佳答案
对于虚拟方法 onEvent,您在 Observer 的通知和构造函数之间进行竞争:
testObs还没有完全构造(虚方法初始化),但是它已经被Observer构造器注册到Center中。
要调试它,请尝试在您的构造函数中添加打印语句。
要修复它:在 testObs 的构造函数而不是 Observer 的构造函数中注册您的 testObs。
析构函数也有同样的问题:将注销移到testObs的析构函数
或者提供一个空的 onEvent 实现:
virtual void onEvent(Event*){ };
但归根结底,在对象实例完全构造之前就暴露它是一个糟糕的设计。
关于c++ - 通过多个线程自行取消/注册观察者,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25667948/