Потокобезопасный вектор

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

Как я понимаю вещи, std:: vector будет перераспределить память при возврате новых элементов, что является моим случаем, если я не зарезервировал достаточно места (что не моем случае).

У меня есть вектор std:: shared_ptr, и этот вектор содержит уникальные объекты (или, точнее, указатели на уникальные объекты в вектор.)

обработка этих объектов с помощью указателей обернута вокруг класса Factory & Handler, но указатели на объекты доступны извне класса-оболочки и могут иметь измененные значения членов. Там нет удаления происходит в любое время.

Если я правильно понимаю проблемы, поднятые в предыдущих вопросах SO о std:: vector и безопасности потоков, добавление (push_back) новых объектов мая аннулировать предыдущие указатели, как вектор внутренне может перераспределить память и скопировать все, что было бы, конечно, катастрофой для меня.

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

и

  1. использование атомарных или мьютексов недостаточно? Если я отталкиваюсь от одного потока, другой поток, обрабатывающий объект с помощью указателя, может оказаться недопустимым объект?
  2. есть ли библиотека, которая может обрабатывать эту форму проблем MT? Я продолжаю читать о TBB Intel, но поскольку я уже использую C++11, я хотел бы свести изменения к минимуму, даже если это означает больше работы с моей стороны - я хочу учиться в процессе, а не просто копировать-вставлять.
  3. кроме блокировки доступа при изменении объектов, я хотел бы асинхронный параллельный доступ для чтения к вектору, который не будет признан недействительным push_backs. Как я могу достичь это?

Если это имеет какое-либо значение, все вышеперечисленное находится в linux (debian jessie) с помощью gcc-4.8 с включенным c++11.

Я открыт для использования минимально инвазивных библиотек.

большое спасибо заранее :-)

3 ответов


добавить (push_back) новых объектов может привести к аннулированию предыдущей указатели ...

нет, эта операция не аннулирует предыдущие указатели, если вы не ссылаетесь на адреса внутри внутреннего управления данными векторов (что явно не является вашим сценарием).
Если вы храните необработанные указатели или std::shared_ptrтам, те будут просто скопированы, и не получить недействительным.


как указано в комментариях a std::vector не очень подходит для гарантируйте безопасность резьбы для моделей производителя / потребителя по ряду причин. Ни хранение необработанных указателей для ссылки на живые экземпляры!

A очереди будет гораздо лучше, чтобы поддержать это. Что касается стандартов, вы можете использовать std::deque иметь ceratain точки доступа (front(),back()) для производителя / потребителя.

чтобы сделать поток этих точек доступа безопасным (для нажатия / выскакивания значений), вы можете легко обернуть их своими класс и используйте мьютекс вместе, чтобы защитить операции вставки / удаления в ссылке общей очереди.
Другой (и главный, как из вашего вопроса) пункт: управление собственностью и временем жизни содержащихся / ссылочных экземпляров. Вы также можете передать право собственности потребителю, если это подходит для вашего случая использования (таким образом, выходя из накладных, например,std::unique_ptr), см. ниже ...

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


'1. Использование атомарных или мьютексов недостаточно? Если я отталкиваюсь от одного потока, другой поток, обрабатывающий объект с помощью указателя, может иметь недопустимый объект?'

время жизни (и, следовательно, потокобезопасное использование) экземпляров, хранящихся в очереди (общий контейнер), должно управляться отдельно (например, с помощью интеллектуальных указателей, таких как std::shared_ptr или std::unique_ptr хранящуюся там).

'2. Есть библиотека ...'

это может быть достигнуто все хорошо с существующими стандартными библиотечными механизмами IMHO.

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

не стесняйтесь просить больше разъяснений ...


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

например, это может выглядеть так:

concurrent_vector<Object*>:
  size_t m_baseSize = 1000
  size_t m_size     = 3500
  part*  m_parts[6] = {
    part* part1, ----> Object*[1000]
    part* part2, ----> Object*[2000]
    part* part3, ----> Object*[4000]
    NULL,
    ...
  }

класс содержит фиксированный массив указателей на отдельные куски памяти с предметы, экспоненциально увеличивающиеся в размерах. Здесь предел 6 частей, поэтому 63000 деталей-но это можно изменить легко.

контейнер начинается с указателей всех деталей, установленных в NULL. Если элемент добавлен, создается 1-й кусок с размером m_baseSize, здесь 1000, и сохранено в m_parts[0]. Последующие пункты написаны там.

когда кусок заполнен, выделяется другой буфер, вдвое больше предыдущего (2000), и сохраняется в m_parts[1]. Это повторяется как требуемый.

все это можно сделать с помощью атомарных операций, но это конечно сложно. Это может быть проще, если все авторы могут быть защищены мьютексом, и только читатели полностью совпадают (например, если запись намного более редкая операция). Все потоки чтения всегда видят либо NULL в m_parts[i] или NULL в одном из буферов или допустимый указатель. Существующие элементы никогда не перемещаются в памяти, недействительны или что-то еще.


Что касается существующих библиотек, вы можете захотеть посмотреть Нить Строительные Блоки Intel, особенно его класс concurrent_vector содержатся. Сообщается, что он имеет следующие функции:

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

перечитывая вопрос, ситуация кажется немного другой.

std::vector плохо подходит для хранения объекты на который вы должны были бы сохранить ссылки, так как push_back может аннулировать все ссылки на сохраненные объекты. Но, вы храните кучу std::shared_ptr.

на std::shared_ptrs, хранящиеся внутри, должны обрабатывать изменение размера изящно (они перемещаются, но не объекты, на которые они указывают), пока в потоках вы не держите ссылки std::shared_ptrs хранится внутри вектора, но вы держите копии из них.

как использовать std::vector и std::deque вы должны синхронизировать доступ к структуре данных, так как push_back, хотя и не делает ссылку недействительной, изменяет внутренние структуры deque, и, таким образом, не разрешается работать одновременно с доступом deque.

OTOH,std::deque наверное, лучше всего подходит для повышения производительности; на каждый размер вы движетесь вокруг много std::shared_ptr, который, возможно, придется сделать заблокированный инкремент / декремент для пересчета в случае копирования / удаления (если они перемещены-как они должны - это должно быть отменено - но YMMV).

но самое главное, если вы используете std::shared_ptr просто, чтобы избежать потенциальных движений в векторе, вы можете полностью отказаться от него при использовании deque, поскольку ссылки не являются недействительными, поэтому вы можете хранить свои объекты непосредственно в deque вместо использования кучи распределение и косвенные / накладные расходы std::shared_ptr.