Потоковая безопасность записи std::vector vs plain array - программирование
Подтвердить что ты не робот

Потоковая безопасность записи std::vector vs plain array

Я прочитал qaru.site/info/177928/..., что ни один из контейнеров STL не является потокобезопасным для записи. Но что это значит на практике? Означает ли это, что я должен хранить записываемые данные в простых массивах?

Я ожидаю, что одновременные вызовы std::vector::push_back(element) могут привести к несогласованным структурам данных, потому что это может повлечь изменение размера вектора. Но как насчет такого случая, когда изменение размера не задействовано:

1), используя массив:

int data[n];
// initialize values here...

#pragma omp parallel for
for (int i = 0; i < n; ++i) {
    data[i] += func(i);
}

2) с помощью ` std::vector` `:

std::vector<int> data;
data.resize(n);
// initialize values here...

#pragma omp parallel for
for (int i = 0; i < n; ++i) {
    data[i] += func(i);
}

Является ли первая реализация действительно лучше, чем вторая, а) с точки зрения безопасности потоков и б) с точки зрения производительности? Я бы предпочел использовать std::vector, так как я менее удобен для массивов в стиле C.

EDIT: я удалил #pragma omp atomic update, защищающий запись.

4b9b3361

Ответ 1

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

В стандартном варианте для функций-членов контейнеров не должно быть потокобезопасным. В этом случае вы используете vector<int>::operator[], поэтому вам нужна явная гарантия безопасности потоков для этого члена, что кажется разумным, поскольку его вызов даже на неконстантном векторе не изменяет сам вектор. Поэтому я сомневаюсь, что в этом случае есть проблема, но я не искал гарантии [edit: rici found it]. Даже если это потенциально опасно, вы можете сделать int *dataptr = &data.front() перед циклом, а затем индексировать dataptr вместо data.

В стороне, этот код не гарантируется для vector<bool> безопасным, так как это специальный случай, для которого несколько элементов сосуществуют внутри одного объекта. Это было бы безопасно для массива bool, поскольку разные элементы этого являются разными "ячейками памяти" (1.7 в С++ 11).

Ответ 2

В С++ 11, который определяет правила для расчётов данных, описана безопасность потоков контейнеров. Соответствующим разделом стандарта является & sect; 23.2.2, пункт 2:

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

[Примечание: для вектора <int> x с размером больше единицы, x [1] = 5 и * x.begin() = 10 могут выполняться одновременно без гонки данных, но x [0] = 5 и * x.begin() = 10 выполняются одновременно может привести к гонке данных. В качестве исключения из общего правила для вектора <bool> y, y [0] = true может участвовать в гонке с y [1] = true. -end note]

Упомянутая секта; 17.6.5.9 по существу запрещает любую параллельную модификацию любым стандартным интерфейсом библиотеки, если это специально не разрешено, поэтому в разделе, который я цитирую, вы точно указываете, что разрешено (и это включает ваше использование).

Поскольку вопрос поднял Стив Джессоп, пункт 1 раздела " 23.2.2 явно разрешает одновременное использование [] в контейнерах последовательностей:

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

Ответ 3

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

Ответ 4

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

std::vector<int> data(n, 0);

resize() хорошо работает. Производительность будет одинаковой. Причина, по которой многопоточный доступ не приведет к повреждению вектора, заключается в следующем: ваши данные расположены на своем месте и не будут двигаться оттуда. Точки OMP не будут обращаться к одному элементу за раз.