2010-10-11 2 views
3

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

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; 
} 

ПРИМЕНЕНИЕ:

RequestList* requests; 

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

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

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

+4

- это список, измененный? – Nikko

+0

В этой конкретной теме верхний элемент удаляется каждый раз ... –

+0

Ваш пример кода кажется странным. Если бы вы могли объяснить, почему ваш класс нуждается в члене итератора, ваш вопрос может быть более правильным ответом. –

ответ

2

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

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

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

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

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

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

+0

Великие умы думают одинаково ... –

3

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

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

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

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

1

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

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

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

0

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

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

+0

Что вы, говорят, кажется, противоречит другим ответам ...вы можете уточнить? –

+0

@ Тони: Это кажется таким, потому что я сделал акцент на некоторых других вещах. Другие ответы в основном указывают на трудности * работы * с итератором, тогда как я только замалчиваю это. Из моего ответа я удалил, казалось бы, противоречивую часть. –

0

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

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

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

0

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

Смежные вопросы