我正在尝试从 observer pattern 编写一个安全的 Subject
类.我想知道使用 weak_ptr
是否是存储 IObserver
实例的最佳方式:
IObserver
实例在释放后无法使用。Subject
类不保留应该释放的IObserver
引用 (lapsed listener problem)。Subject
类必须是线程安全的。
很遗憾,我们的编码标准规定我们不允许使用 boost。我想我前世是个坏人。幸运的是,我可以使用 C++11(Visual Studio 2012 附带的)。
这是一个示例 Observer
类。
// Observer interface that supports notify() method
class IObserver
{
public:
virtual void notify() const = 0;
virtual ~IObserver() {}
};
// Concrete observer implementation that prints a message
class Observer : public IObserver
{
public:
Observer( const std::string& message) : m_message( message ){}
void notify() const {
printf( "%s\r\n", m_message.c_str() );
}
private:
std::string m_message;
};
这里是 Subject
类。
// Subject which registers observers and notifies them as needed.
class Subject
{
public:
// Use shared_ptr to guarantee the observer is valid right now
void registerObserver( const std::shared_ptr<IObserver>& o )
{
std::lock_guard<std::mutex> guard( m_observersMutex );
m_observers.push_back( o );
}
void unregisterObserver( const std::shared_ptr<IObserver>& o )
{
std::lock_guard<std::mutex> guard( m_observersMutex );
// Code to remove the observer from m_observersMutex
}
// This is a method that is run in its own thread that notifies observers of some event
void doNotify()
{
std::lock_guard<std::mutex> guard( m_observersMutex );
// Notify any valid observers of events.
std::for_each( m_observers.cbegin(), m_observers.cend(),
[]( const std::weak_ptr<IObserver>& o )
{
auto observer = o.lock();
if ( observer ) {
observer->notify();
}
} );
// Remove any dead observers. These are ones which have expired().
m_observers.erase( std::remove_if( m_observers.begin(), m_observers.end(),
[]( const std::weak_ptr<IObserver>& o )
{
return o.expired();
} ), m_observers.end() );
}
private:
std::vector<std::weak_ptr<IObserver>> m_observers;
std::mutex m_observersMutex;
};
这是一些练习Subject
的代码:
int main(int argc, wchar_t* argv[])
{
Subject subject;
auto observerHello = std::make_shared<Observer>( "Hello world" );
subject.registerObserver( observerHello );
{
// Create a scope to show unregistration.
auto observerBye = std::make_shared<Observer>( "Good bye" );
subject.registerObserver( observerBye );
subject.doNotify();
}
printf( "%s\r\n", "Observer good bye is now be destructed" );
subject.doNotify();
return 0;
}
我对 weak_ptr
的使用是线程安全的吗?从这里 https://stackoverflow.com/a/2160422/1517648我认为是的。
这是解决失效听众问题的合法方法吗?
最佳答案
我对你的 doNotify
有点怀疑——假设你解雇的观察者中的某些东西最终会添加或删除观察者? -- 坏事发生(包括崩溃)。或者阻止另一个线程的操作,谁阻止尝试添加观察者? -- 坏事发生(死锁!)
这很难解决。基本上是重入的问题。
当您持有锁时,永远不要离开对代码的控制权。在调用回调时持有锁是不行的。
所以,至少:
锁定,然后复制您的列表,然后解锁。在执行此拷贝时,您还可以删除过期的观察者(从原始列表和拷贝列表中)。
然后从复制的列表中触发观察者。
这留下了一些 Unresolved 问题。比如移除一个观察者并不能保证它以后不会被调用!这只是意味着最终它不会被调用。
这有多重要取决于你如何使用倾听。
一种可行的方法是包含添加/删除/通知/killthread 事件的任务队列(使 killthread 成为队列中的任务可以大大减少关闭的烦人)。现在所有同步都在队列中。如果您无法编写非阻塞无锁队列,则通知代码可以简单地锁定、std::move
队列、解锁,然后继续执行它。或者你可以写一个队列,让 pop
阻塞直到有东西要读,而 push
不会阻塞。
快速而肮脏的“复制和广播”可能如下所示:
std::vector<std::shared_ptr<IObserver>> targets;
{
std::lock_guard<std::mutex> guard( m_observersMutex );
m_observers.erase( std::remove_if( m_observers.begin(), m_observers.end(),
[&targets]( const std::weak_ptr<IObserver>& o )
{
std::shared_ptr<IObserver> ptr = o.lock();
if (ptr) {
targets.push_back(ptr);
return false;
} else {
return true;
}
} ), m_observers.end() );
}
for( auto& target:targets ) {
target->notify();
}
关于c++ - 使用weak_ptr的观察者模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14381588/