Получение блокировки путем проверки состояния и повторной проверки
Является ли что-то вроде этого действительным:
std::vector<std::vector<int>> data;
std::shared_mutex m;
...
void Resize() {
// AreAllVectorsEmpty: std::all_of(data.begin(), data.end(), [](auto& v) { return v.empty(); }
if (!AreAllVectorsEmpty()) {
m.lock();
if (!AreAllVectorsEmpty()) {
data.resize(new_size);
}
m.unlock();
}
}
я проверяю AreAllVectosEmpty()
и затем, если условие выполнено успешно, затем берется блокировка, а затем снова проверяется на то же условие, выполнять ли изменение размера.
Будет ли это потокобезопасным? Resize
вызывается только одним потоком, но другие потоки манипулируют элементами data
,
Это требование, которое AreAllVectorsEmpty
иметь забор памяти или овладеть семантикой?
Редактировать: другие темы будут блокироваться, когда m.lock
приобретается Resize
,
Изменить: Давайте также предположим, new_size
достаточно велик, чтобы произошло перераспределение.
Изменить: Обновить код для shared_mutex.
Редактировать: AreAllVectorsEmtpy
перебирает вектор данных. Никто другой не изменяет вектор данных, но данные [0], данные [1] и т. Д. Изменяются другими потоками. Я предполагаю, что переменная размера data[0] находится внутри вектора и представляет собой простое целое число, поэтому безопасно обращаться к data[0].size(), data[1].size() и т. Д. В Resize
нить. AreAllVectorsEmpty
перебирает data
и проверка vector.empty()
,
4 ответа
Ответ полностью зависит от того, как AreAllVectorsEmpty
реализовано.
Если он просто проверяет флаг, который может быть установлен атомарно, то да, это безопасно. Если он выполняет итерацию по вектору, который вы намереваетесь изменить (или другим часто используемым контейнерам), то нет, это небезопасно (что происходит с итераторами, если вектор выполняет внутреннее перераспределение???).
Если вы делаете последнее, вам нужен механизм блокировки чтения / записи, посмотрите на общие мьютексы.
Затем вы получите общую блокировку перед проверкой, а в случае модификации - эксклюзивную блокировку.
Знать, что если areAllVectorsEmpty
использует некоторую независимую структуру данных (кроме упомянутого атомарного флага), вам, возможно, придется защищать ее с помощью отдельного мьютекса.
Я хотел бы использовать shared_mutex и использовать:
- общая блокировка во всех потоках, которые просто читают вектор (при чтении вектора)
- уникальный замок в этой теме при изменении размера вектора
Я думаю, что сначала проверка размера, а затем изменение его размера безопасна при условии, что это единственный поток, который изменяет содержимое вектора.
Блокировка автоматически подразумевает барьер памяти, иначе блокировка не имела бы особого смысла.
Стандарт не требует, чтобы это работало, сравните http://en.cppreference.com/w/cpp/container. Если это работает с вашим конкретным компилятором и STL? Вам нужно будет заглянуть в источники. Но я бы на это не рассчитывал.
Это подводит меня к вопросу: почему вы хотите это сделать? По причинам производительности? Вы измерили производительность? Действительно ли это измеримое снижение производительности при блокировке перед вызовом? AreAllVectorsEmpty
?
Кстати, пожалуйста, не блокируйте мьютекс напрямую, используйте std:: lock_guard.
// AreAllVectorsEmpty: std::all_of(data.begin(), data.end(), [](auto& v) { return v.empty(); }
Вы получаете доступ к внутренним элементам внутренних векторов (вызывая пустой), и в то же время другой поток может вставить некоторые элементы в один из внутренних векторов -> гонка данных.