C ++ Потокобезопасный vector.erase - PullRequest
1 голос
/ 22 января 2012

Я написал многопоточный рендерер для SFML, который берет указатели на прорисовываемые объекты и сохраняет их в векторе для рисования каждого кадра. Начало добавления объектов к вектору и удаление объектов к вектору часто приводит к ошибкам сегментации (SIGSEGV). Чтобы попытаться бороться с этим, я бы добавил объекты, которые нужно было удалить / добавить в очередь, которая будет удалена позже (до рисования кадра). Это, казалось, исправило это, но в последнее время я заметил, что, если я добавлю много объектов за один раз (или добавлю / уберу их достаточно быстро), я получу тот же SIGSEGV.

Должен ли я использовать блокировки при добавлении / удалении из вектора?

Ответы [ 2 ]

3 голосов
/ 22 января 2012

Вы должны понимать, что потокобезопасность гарантирует стандарт C ++ (и реализации C ++ 2003 для возможно параллельных систем).Стандартные контейнеры являются поточно-ориентированными в следующем смысле:

  1. Можно иметь несколько параллельных потоков, читающих один и тот же контейнер.
  2. Если существует один поток, модифицирующий контейнерне должно быть одновременных потоков, читающих или пишущих один и тот же контейнер.
  3. Различные контейнеры не зависят друг от друга.

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

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

if (!c.empty() {
    auto value = c.back();
    // do something with the read value
}

Контейнер может контролировать доступ к вызовам на empty() и back().Тем не менее, между этими вызовами необходимо обязательно освободить любые средства синхронизации, т. Е. К тому моменту, когда поток попытается прочитать c.back(), контейнер может снова оказаться пустым!Существуют два основных способа решения этой проблемы:

  1. Необходимо использовать внешнюю блокировку, если существует вероятность того, что параллельный поток может изменять контейнер для охвата всего диапазона обращений, которые взаимозависимы внекоторая форма.
  2. Вы изменяете интерфейс контейнеров, чтобы они стали мониторами .Однако интерфейс контейнера не подходит для изменения в этом направлении, потому что мониторы по существу поддерживают только интерфейсы типа «запусти и забудь».

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

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

1 голос
/ 22 января 2012

Должен ли я использовать блокировки при добавлении / удалении из вектора?

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

...