Вернуть итератор в многопоточную среду, хорошая идея? - PullRequest
3 голосов
/ 11 октября 2010

Является ли хорошей идеей возвращать итератор в списке объекта, который используется и используется в многопоточной среде?

class RequestList
{
public:
    RequestList::RequestList();
    RequestList::~RequestList();

    std::list<boost::shared_ptr<Request> >::iterator GetIterator();
    int ListSize();
    void AddItem(boost::shared_ptr<Request> request);
    void RemoveItem(boost::shared_ptr<Request> request);
    std::list<boost::shared_ptr<Request> > GetRequestsList();
    boost::shared_ptr<Request> GetRequest();

private:
    std::list<boost::shared_ptr<Request> > requests;
    std::list<boost::shared_ptr<Request> >::iterator iter;   //Iterator
    boost::mutex listmtx;
};




std::list<boost::shared_ptr<Request> >::iterator RequestList::GetIterator()
{
    return this->iter;
}

USE:

RequestList* requests;

В какой-то теме (может использоваться снова в других темах)

 std::list<boost::shared_ptr<Request> >::iterator iter = requests->GetIterator();

Или было бы разумнее просто создавать итератор для этого списка каждый раз и использовать его локально в каждом потоке?

Ответы [ 6 ]

3 голосов
/ 11 октября 2010

Нет, обычно не рекомендуется разделять итератор между потоками.Есть несколько способов убедиться, что у вас нет проблем.

Во-первых, итератор - это легкий объект, который быстро построить и занимает очень мало памяти.Таким образом, вы не должны беспокоиться о проблемах производительности.Просто создайте экземпляр всякий раз, когда он вам нужен.

При этом вы должны быть уверены, что ваш список не будет изменен во время итерации.Я вижу, у вас есть boost::mutex в вашем классе.Блокировка, которая идеально подойдет для обеспечения того, чтобы у вас не возникало проблем при итерации.

Другой и в равной степени действительный способ справиться с этими ситуациями - просто скопировать внутренний список и выполнить итерацию.Это хорошее решение, если вам требуется, чтобы список постоянно обновлялся, и вы не хотите, чтобы другие темы ожидали.Конечно, это занимает немного больше памяти, но поскольку вы храните умные указатели, вряд ли это будет что-то вообще.

2 голосов
/ 11 октября 2010

Зависит от того, как используется список, но из того, что вы показали, выглядит неправильно.Итератор становится недействительным, если удаляется элемент, на который он ссылается: в этом случае элемент является shared_ptr объектом в списке.

Как только вы освободите мьютекс, я думаю, что может появиться какой-то другой потоки удалите этот элемент.Вы не показали код, который это делает, но если это может произойти, то итераторы не должны «избегать» мьютекса.

Я предполагаю, что это «самосинхронизирующийся» контейнер, так как мьютекс является частными нет ничего в API, чтобы заблокировать это.Основная трудность в таких вещах состоит в том, что не является потокобезопасным для выполнения какой-либо итерации с ними извне.Достаточно просто создать потокобезопасную очередь, которая поддерживает:

  • добавление элемента,
  • удаление элемента по значению,
  • удаление головы и возвраткопия его значения.

Кроме того, сложнее обеспечить полезные базовые операции, потому что почти все, что манипулирует списком любым интересным способом, должно выполняться полностью под замком.

Судя по всему, вы можете скопировать список с помощью GetRequestsList и перебрать копию.Не уверен, принесет ли это вам пользу, так как копия мгновенно устарела.

1 голос
/ 11 октября 2010

Доступ к списку через итераторы в нескольких потоках, где сам главный список не заблокирован, опасен.

Нет никакой гарантии, в каком состоянии будет список, когда вы будете работать с итераторами в разных потоках (например, один поток мог бы успешно пройти через и удалить все элементы, чем будет заниматься другой поток - кто такжеитерации, понимаете?)

Если вы собираетесь работать со списком в нескольких потоках, сначала заблокируйте весь список, а затем делайте то, что вам нужно.Копирование списка является вариантом, но не оптимальным (в зависимости от размера вашего списка и скорости его обновления).Если блокировка становится узким местом, переосмыслите свою архитектуру (например, список на поток?)

0 голосов
/ 11 октября 2010

Я бы пересмотрел этот дизайн и использовал бы подход, основанный на задачах. Таким образом, вам не нужны никакие мьютексы. Например, используйте Intel TBB, который инициализирует внутренний пул задач. Таким образом, вы можете легко реализовать концепцию «один автор / несколько читателей». Сначала добавьте все запросы в ваш контейнер запросов (простой std :: vector может лучше подходить с точки зрения локальности и производительности кэша), а затем выполните Parallel_for () над вашим вектором запросов, НО НЕ удаляйте запрос в вашем для () функтора!
После обработки вы можете очистить вектор запроса без необходимости блокировки мьютекса. Вот и все!
Я надеюсь, что смогу немного помочь.

0 голосов
/ 11 октября 2010

Если список изменяется одним из ваших потоков, у вас могут возникнуть проблемы.

Но, конечно, вы можете позаботиться об этом, установив блокировки и ro- и rw-блокировки во время модификации.Но так как мьютексы являются бичом любой высокопроизводительной программы, может быть, вы можете сделать копию списка (или ссылок) и оставить исходный список без мьютекса и без блокировки?Это было бы наилучшим способом.

Если у вас есть мьютексы, вам нужно бороться только с проблемами изменения списка, удерживая итераторы на нем, как вы это обычно делаете, то есть добавление элементов должновсе в порядке, удаление должно быть сделано "осторожно", но выполнение этого на list, вероятно, менее вероятно взорвется, чем на vector: -)

0 голосов
/ 11 октября 2010

Каждый поток, который вызывает функцию GetIterator, получит свою собственную копию сохраненного итератора в списке. Поскольку std::list<>::iterator является двунаправленным итератором, любые сделанные вами копии полностью независимы от источника. Если один из них изменится, это не будет отражено ни в одном из других итераторов.

Что касается использования итератора в многопоточной среде, это ничем не отличается от однопоточной среды. Итератор остается действительным, пока элемент, на который он ссылается, является частью контейнера. Вам просто нужно позаботиться о правильной синхронизации при доступе / изменении контейнера или его элементов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...