Как перебрать контейнер потокобезопасным способом? - PullRequest
8 голосов
/ 14 апреля 2011

У меня есть контейнер (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() вызывают асинхронно.Какую структуру данных я могу использовать, чтобы во время итерации my_stuff был «защищен» от операций добавления или удаления?

Ответы [ 3 ]

15 голосов
/ 14 апреля 2011

звучит как блокировка чтения / записи необходима. По сути, идея заключается в том, что у вас может быть 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));
   }
};

Другим вариантом будет сделать общедоступной блокировку считывателя / записывающего устройства и поручить вызывающей стороне правильно использовать блокировку. Но это более подвержено ошибкам.

4 голосов
/ 14 апреля 2011

ИМХО, было бы ошибкой иметь частный мьютекс в классе структуры данных, а затем писать методы класса, чтобы все было поточно-ориентированным независимо от того, что делает код, вызывающий методы. Сложность, которая требуется, чтобы сделать это полностью и безупречно, слишком велика.

Более простой способ - иметь общедоступный (или глобальный) мьютекс, за который вызывающий код отвечает за блокировку, когда ему требуется доступ к данным.

Здесь - моя статья в блоге на эту тему.

3 голосов
/ 14 апреля 2011

Когда вы возвращаете список, возвращайте его, заключенный в класс, который блокирует / разблокирует мьютекс в его конструкторе / деструкторе. Что-то вроде

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.

О, и блокировка чтения / записи действительно лучшее решение, чем мьютекс.

...