Потоковая безопасность чтения и записи из / в std :: map в C ++ - PullRequest
2 голосов
/ 17 февраля 2012

У меня есть многопоточное приложение, которое использует общую структуру данных, которая упаковывает std :: map.

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

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

Однако 3-й поток одновременно пытается удалить 3-й другой объект. Она приобрела блокировку для этого, так что никакой другой поток не будет пытаться читать, писать или удалять его.

Является ли потокобезопасным удаление элемента из карты, когда другие потоки читают / записывают в другие элементы карты?

Ответы [ 3 ]

5 голосов
/ 17 февраля 2012

Обратите внимание, что вы знаете , что контейнеры STL не являются поточно-ориентированными, неправильно! В C ++ 2011 контейнер предоставляет разумные гарантии безопасности потоков. Они могут отличаться от ваших желаний, но они разумны и важны:

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

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

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

2 голосов
/ 17 февраля 2012

Нет.Как указал Diemtar Kühl, вам нужен замок на контейнере, который должен быть получен при каждом доступе к контейнеру.Таким образом, ваш сценарий для большинства доступа будет (C это блокировка контейнера, O блокировка для отдельного объекта:

acquire C
find object
acquire O
release C
process object
release O

и, если вы удалите, либо:

acquire C
find object
acquire O
delete object
release O
release C

, или если вам нужно обработать сначала, прежде чем принять решение об удалении:

acquire C
find object
acquire O
release C
process, determine that deletion is needed
acquire C
release O
release C

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

  • Если вашим потокам требуется одновременный доступ к нескольким объектам. В этом случае в первом сценарии у вас есть поток 1, который получает C, затем получает O1, а затем освобождает CПосле этого поток 2 получает C, затем блокирует на O1. Поток 1 затем возобновляет работу и решает, что ему также необходим доступ к объекту 2. Таким образом, он пытается получить C и блокирует, ожидаяд 2 отпустить.(Поток 2, конечно, блокируется, пока поток 1 не освободит O1.)

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

Если ни один из потоков не блокирует более одного объекта и первый сценарий используется для удаления, шаблон будетРабота.Но это очень хрупко - слишком легко представить программиста по обслуживанию, нарушающего одно из этих условий, - и я настоятельно рекомендую против этого.(Конечно, если ни одна из альтернатив не обеспечивает достаточную пропускную способность, вам, возможно, придется ее использовать. И даже второй сценарий удаления можно заставить работать , если вы отпускаете O перед попыткой второго получения C,затем снова получите O, если у вас есть C. Основные условия: вы всегда должны приобретать C, затем O в этом порядке и никогда не пытаться приобрести C, когда у вас есть O.)

Также обратите внимание, чтоНаличие каждого объекта, содержащего мьютекс, может быть непростым делом, так как вы должны удерживать мьютекс до тех пор, пока объект не будет удален с карты.Это означает, что либо сама карта содержит указатель на объекты (и сохраняет указатель на объект после удаления его с карты и освобождает блокировку и удаляет объект через этот указатель), либо объект сохраняет указатель на объект.mutex.

Самое простое решение - просто использовать одну блокировку на C и поддерживать ее во время обработки O. Если обработка не слишком длинная, это может быть приемлемо;если вы используете многопоточность по той причине, что можете обрабатывать несколько ядер одновременно, это не сработает.

В противном случае вы можете рассмотреть возможность использования rwlock для контейнера и удержанияв течение всего времени, в течение которого вы удерживаете O. Затем можно продолжить простой доступ, поскольку это только доступ для чтения, а блокировка допускает множественный доступ для чтения.Для удаления вам понадобится доступ на запись, который будет блокироваться до тех пор, пока не завершатся все доступы на чтение;вам также все еще понадобится специальная обработка для второго сценария удаления, поскольку попытка обновить доступ с чтения на запись может привести к взаимоблокировке, как описано выше.(Чтобы перейти от чтения к записи, необходимо, чтобы никакой другой поток не имел права на чтение.)

0 голосов
/ 17 февраля 2012

Потокобезопасно ли удаление элемента из карты, когда другие потоки читают / записывают в другие элементы карты?

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

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