问题,有两种途径。一是使用线程安全容器,另一种是我们在BasicSubject的适当地方放置锁。我只讨论后一种情况。
为了让代码具有一定的灵活性,我们使用泛型编程中常用的Policies技术。第一步将锁类定义出来:
01.struct NullLocker{ 02. inline void lock() {}; 03. inline void unlock() {}; 04.}; 05. 06.struct CriticalSectionLocker{ 07. CriticalSectionLocker() {::InitializeCriticalSection(&cs_);} 08. ~CriticalSectionLocker() {::DeleteCriticalSection(&cs_);} 09. inline void lock() {::EnterCriticalSection(&cs_);} 10. inline void unlock() {::LeaveCriticalSection(&cs_);} 11.private: 12. CRITICAL_SECTION cs_; 13.};
前者为空锁,用于单线程环境中。后者借助Windows平台中的临界区实现进程内的锁语义。你也可以再增加进程间的锁语义。
接着便是将我们的BasicSubject类修改成如下样子:
01.template < 02. class ObserverT, 03. class LockerT = NullLocker, 04. class ContainerT = std::list<ObserverT *> 05.> 06.class BasicSubject : protected LockerT { 07.public: 08. inline void addObserver(ObserverT &observer) { 09. lock(); 10. observers_.push_back(&observer); 11. unlock(); 12. } 13. 14. inline void removeObserver(ObserverT &observer) { 15. lock(); 16. ... 17. unlock(); 18. } 19. 20.protected: 21. template <typename ReturnT> 22. inline void notifyAll(ReturnT (ObserverT::*pfn)()) { 23. lock(); 24. for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it) 25. ((*it)->*pfn)(); 26. unlock(); 27. } 28. ... 29.};
默认的锁类是NullLocker,也就是运行在单线程环境中。需要工作在多线程中时可像这样使用:
class Widget : protected BasicSubject<MouseListener, CriticalSectionLocker> {...};
(三)更新方法修改观察者链表
想像一下当观察者在接收到通知而立即修改主题中的观察者链表时会发生什么?因为主题是通过对已注册的观察者链表迭代而逐个通知观察者的相应更新方法的,换句话说,在迭代进行中观察者就去修改观察者链表。这个问题类似于这样的代码设计:
01.std::list<int> is = ... 02.for (std::list<int>::iterator it = is.begin(); it != is.end(); ++it) { 03. is.erase(std::remove(is.begin(), is.end(), 2), is.end()); 04.}
危险!迭代器在链表被修改后有可能失效。
也许你会疑虑,在使用了(二)中所提的锁机制之后不就不会有此问题了吗?实际情况是,锁对于此类问题没有任何作用。
解决此类问题的最好办法是使用不会因容器本身被修改而促使迭代器失效的容器。然而,就目前来说,标准STL库中的所有容器都不属此类。因此,我们有必要花点心思处理此类问题。
当链表处于被迭代过程中时,对链表的修改动作先被记录下来,等到链表迭代完毕后再回过头执行先前记录下来的修改动作,如果对链表的修改动作不是发生在迭代过程中,就按普通方式处理。依据此思想,代码可像这样实现:
01.template < 02. ... 03.> 04.class BasicSubject : protected LockerT 05.{ 06.public: 07. BasicSubject() : withinLoop_(false) {} 08. 09. void addObserver(ObserverT &observer) { 10. lock(); 11. if (withinLoop_) 12. modifyActionBuf_.insert(std::make_pair(true, &observer)); 13. els |