如何以线程安全的方式遍历容器?

| 我有一个容器(C ++),需要用两种方法在不同的线程上进行操作:1)添加和删除元素,以及2)遍历其成员。显然,在发生迭代时删除元素=灾难。代码看起来像这样:
class A
{
public:
   ...
   void AddItem(const T& item, int index) { /*Put item into my_stuff at index*/ }
   void RemoveItem(const T& item) { /*Take item out of m_stuff*/ }
   const list<T>& MyStuff() { return my_stuff; } //*Hate* this, but see class C
private:
   Mutex mutex; //Goes in the *Item methods, but is largely worthless in MyStuff()
   list<T> my_stuff; //Just as well a vector or deque
};
extern A a; //defined in the .cpp file

class B
{
   ...
   void SomeFunction() { ... a.RemoveItem(item); }
};

class C
{
   ...
   void IterateOverStuff()
   {
      const list<T>& my_stuff(a.MyStuff());
      for (list<T>::const_iterator it=my_stuff.begin(); it!=my_stuff.end(); ++it)
      {
          ...
      }
   }
};
同样,
B::SomeFunction()
C::IterateOverStuff()
被异步调用。我可以使用什么数据结构来确保在迭代过程中“ 3”不受添加或删除操作的“保护”?     
已邀请:
听起来好像需要读/写器锁。基本上,您的想法是您可能有1个或更多读者,或只有一位作家。绝对不能同时拥有读写锁。 编辑:我认为适合您的设计的用法示例涉及进行小的更改。向拥有列表的类中添加一个“ iterate”函数,并使其成为模板,以便您可以传递函数/功能定义每个节点的操作。像这样的东西(快速而肮脏的伪代码,但是您明白了……):
class A {
public:
    ...
    void AddItem(const T& item, int index) {
        rwlock.lock_write();
        // add the item
        rwlock.unlock_write();
    }

    void RemoveItem(const T& item) {
        rwlock.lock_write();
        // remove the item
        rwlock.unlock_write();
    }

    template <class P>
    void iterate_list(P pred) {
        rwlock.lock_read();
        std::for_each(my_stuff.begin(), my_stuff.end(), pred);
        rwlock.unlock_read();
    }

private:
    rwlock_t rwlock;
    list<T> my_stuff; //Just as well a vector or deque
};


extern A a; //defined in the .cpp file

class B {
    ...
    void SomeFunction() { ... a.RemoveItem(item); }
};

class C {
    ...

    void read_node(const T &element) { ... }

    void IterateOverStuff() {
        a.iterate_list(boost::bind(&C::read_node, this));
   }
};
另一种选择是使读取器/写入器锁可公开访问,并使调用方负责正确使用该锁。但这更容易出错。     
恕我直言,在数据结构类中拥有私有互斥体然后编写类方法是错误的,无论调用该方法的代码做什么,整个事情都是线程安全的。完全而完美地执行此操作所需的复杂性远远超过了顶部。 比较简单的方法是拥有一个公共(或全局)互斥锁,当需要访问数据时,调用代码负责锁定该互斥锁。 这是我关于此主题的博客文章。     
返回列表时,将其返回封闭在其构造函数/析构函数中的互斥锁的类中。遵循以下原则
class LockedIterable {
public:
  LockedIterable(const list<T> &l, Mutex &mutex) : list_(l), mutex_(mutex)
  {lock(mutex);}
  LockedIterable(const LockedIterable &other) : list_(other.list_), mutex_(other.mutex_) {
    // may be tricky - may be wrap mutex_/list_ in a separate structure and manage it via shared_ptr?
  }
  ~LockedIterable(){unlock(mutex);}
  list<T>::iterator begin(){return list_.begin();}
  list<T>::iterator end(){return list_.end();}
private:
  list<T> &list_;
  Mutex &mutex_;
};

class A {
  ...
  LockedIterable MyStuff() { return LockedIterable(my_stuff, mutex); }  
};
棘手的部分是编写副本构造函数,这样您的互斥锁不必是递归的。或者只是使用auto_ptr。 哦,读写锁在这里确实比互斥锁更好。     

要回复问题请先登录注册