Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/133.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sql-server/22.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 在通知期间注册/注销观察员时,如何避免死锁?_C++_Multithreading_Observer Pattern - Fatal编程技术网

C++ 在通知期间注册/注销观察员时,如何避免死锁?

C++ 在通知期间注册/注销观察员时,如何避免死锁?,c++,multithreading,observer-pattern,C++,Multithreading,Observer Pattern,我有办法解决这个问题,但我感觉这个问题已经解决了很多次了 我实现了一个观察者模式,类似于: struct IObserver { virtual void notify(Event &event) = 0; } struct Notifier { void registerObserver(IObserver* observer, EventRange range) { lock(_mutex); _observers[observer] = range;

我有办法解决这个问题,但我感觉这个问题已经解决了很多次了

我实现了一个观察者模式,类似于:

struct IObserver {
  virtual void notify(Event &event) = 0;
}

struct Notifier {
  void registerObserver(IObserver* observer, EventRange range) {
    lock(_mutex);
    _observers[observer] = range;
  }

  void deregisterObserver(IObserver* observer) {
    lock(_mutex);
    _observers.erase(observers.find(observer));
  }

  void handleEvent() { /* pushes event onto queue */ }

  void run();

  mutex _mutex;
  queue<Event> _eventQueue;
  map<IObserver, EventRange> _observers;
}
struct IObserver{
虚拟作废通知(事件和事件)=0;
}
结构通知程序{
无效注册表观察者(IObserver*观察者,事件范围){
锁(mutex);
_观察者[观察者]=范围;
}
作废注销观察员(IObserver*观察员){
锁(mutex);
_observer.erase(observer.find(observer));
}
void handleEvent(){/*将事件推送到队列*/}
无效运行();
互斥体(mutex);;
队列事件队列;
地图观察员;
}
run方法是从我创建的线程调用的(它实际上由通知程序拥有)。这个方法看起来像

void Notifier::run() {
  while(true) {
    waitForEvent();
    Event event = _eventQueue.pop();

    // now we have an event, acquire a lock and notify listeners
    lock(_mutex);
    BOOST_FOREACH(map<IObserver, EventRange>::value_type &value, _observers){
      value.first->notify(event);
    }
  }
}
void通知程序::run(){
while(true){
waitForEvent();
事件=_eventQueue.pop();
//现在我们有了一个事件,获取一个锁并通知侦听器
锁(mutex);
BOOST\u FOREACH(映射::值\u类型和值,\u观察员){
value.first->notify(事件);
}
}
}
在notify试图创建一个对象,而该对象又试图注册一个观察者之前,这是非常有效的。在这个场景中,试图获取已经锁定的锁,最终导致死锁。这种情况可以通过使用递归互斥来避免。然而,现在考虑通知触发删除观察者的情况。现在映射迭代器无效


我的问题是,是否有一种模式可以防止这种死锁情况?

我认为这里真正的问题是,当您迭代观察者列表时,有一个事件正在操纵观察者列表。如果正在执行notify(…)操作,则在列表上进行迭代。如果您正在迭代原始列表(而不是副本),则在迭代列表时,注册或注销都会更改列表。我不相信std::map中的迭代器能够很好地处理这个问题

我也遇到过这个问题(仅在单线程上下文中),并且发现解决这个问题的唯一方法是创建一个观察者列表的临时副本并对其进行迭代

我还在迭代过程中缓存了删除的观察者,所以我可以确定,如果我有观察者A、B和C,那么如果A导致C被删除,列表中仍然有C,但C被跳过

我有一个针对单线程应用程序的实现

您只需做一点工作就可以将其转换为线程方法

编辑:我认为多线程应用程序的漏洞在于创建观察者列表的副本(当您输入notify(…)时会这样做),以及在观察者分离时将观察者添加到“最近删除的”列表中。不要在这些函数周围放置互斥体;在这些函数中创建/更新列表的周围放置互斥体,或者仅为此目的创建函数并在其周围放置互斥体

编辑:我还强烈建议创建一些单元测试用例(例如,CPP单元),从多个线程中敲打附加/分离/多分离场景。我必须这样做,以便在我工作时发现一个更微妙的问题

编辑:我特别不尝试处理由于notify(…)调用而添加新观察员的情况。也就是说,有最近删除的列表,但没有最近添加的列表。这样做是为了防止“notify->add->notify->add->etc.”发生,如果有人在构造函数中插入notify,就会发生这种情况

概述了一般方法

代码在github上可用

我已经在几个示例解决方案中使用了这种方法(并且在github上也为其中许多示例解决方案编写了代码)


这有用吗?

这与互斥无关,在单个线程上也会出错。互斥器只是帮你解决了这个问题。作为解决方案,您可以考虑在临时集合中添加观察器,并在循环完成后合并它。还包括。为什么在BOOST_FOREACH之前锁定互斥体?@Oakdale我添加了锁,因为我正在迭代集合。我认为您需要在notify()调用之前锁定循环。deregisterObserver()只应添加到注销列表中,然后在通知您后检查注销列表并删除观察者(所有受互斥保护)。您可能还需要考虑在列表/向量中使用原子来实现细粒度锁定。是的,这很有帮助。我在帖子中提到,我有解决这个问题的想法——这是其中之一:)也许这是对这种方法合乎逻辑的肯定。很高兴能提供帮助。如果您愿意,可以随时查看github代码。