Является ли std :: deque в разных местах памяти одновременно поточно-безопасным?

У меня есть std::deque<std::pair<CustomObj, int>>, который не изменяется в размере при запуске параллельного блока.

Параллельный блок считывает каждый CustomObj deque и устанавливает int.

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

Означает ли это одновременное чтение и запись неопределенного поведения? Должен ли я писать и читать во взаимной зоне отчуждения?

Ответ 1

Удивительно, но в самом современном стандарте есть очень четкий раздел:

(С++ 17, 26.2.2 Гонки данных контейнеров, 2)

  1. Несмотря на 20.5.5.9, Реализации должны избегать расследований данных, когда содержимое содержащегося объекта в разных элементах в одном контейнере, за исключением vector<bool>, изменяется одновременно.

Кроме того, вы можете без проблем вызвать следующие аксессоры:

  1. Для целей предотвращения расследований данных (20.5.5.9) реализации должны учитывать следующие функции: const, begin, end, rbegin, rend, front, back, data, find, lower_bound, upper_bound, equal_range, at и, за исключением ассоциативных или неупорядоченные ассоциативные контейнеры, operator[].

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

Например, это было бы неправильно:

std::dequeue<...> dq;
#pragma omp master
{
    ...
    dq.emplace(...);
}
// no implicit barrier here,
// use omp barrier or change to omp single instead of master
#pragma omp for
for (... i; ...)
    dq[i].second = compute(dq[i]);

Ответ 2

Пока вы можете гарантировать, что размер deque не изменится, и только один поток будет когда-либо писать конкретному элементу, тогда да, это безопасно. У вас есть только проблема при попытке изменить deque (изменить размер) или когда у вас есть несколько потоков чтения и записи в один элемент внутри deque.

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

Ответ 3

Arule для всех стандартных контейнеров:

  • Многие читатели или один писатель на весь контейнер и на каждый элемент.
  • Отдельное считывание или изменение элемента (не добавление/удаление элемента) также считывается в контейнере.

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


В стандарте это обычно формулируется в терминах методов const на контейнере. Метод чтения - const, метод записи не const. Исключением является то, что begin() и end() и data() (методы, которые просто возвращают итераторы), которые являются non- const count as const.

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

В качестве примера случая, когда эмпирическое правило выше "нет", но стандарт говорит "хорошо":

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

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