Получение блокировки путем проверки состояния и повторной проверки - PullRequest
0 голосов
/ 29 мая 2018

Допустимо ли что-то вроде этого:

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 ]

0 голосов
/ 29 мая 2018

// AreAllVectorsEmpty: std :: all_of (data.begin (), data.end (), [] (auto & v) {return v.empty ();}

вы получаете доступ к внутренним элементам внутренних векторов (вызывая пустой), и в то же время другой поток может вставить некоторые элементы в один из внутренних векторов -> гонка данных

0 голосов
/ 29 мая 2018

Ответ полностью зависит от того, как реализован AreAllVectorsEmpty.

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

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

Затем вы получите общую блокировку перед проверкой, а в случае модификации,исключительная блокировка.

Имейте в виду, что если areAllVectorsEmpty использует какую-то независимую структуру данных (кроме упомянутого атомарного флага), вам, возможно, придется защитить ее с помощью отдельного мьютекса .

0 голосов
/ 29 мая 2018

Стандарт не требует, чтобы это работало, сравните http://en.cppreference.com/w/cpp/container#Thread_safety. Если он работает с вашим конкретным компилятором и STL?Вам нужно будет заглянуть в источники.Но я бы не стал на это полагаться.

Это подводит меня к вопросу: почему вы хотите это сделать?По причинам производительности?Вы измерили производительность?Действительно ли это измеримое снижение производительности, когда вы блокируете перед вызовом AreAllVectorsEmpty?

Кстати, пожалуйста, не блокируйте мьютекс напрямую, пожалуйста, используйте std :: lock_guard .

0 голосов
/ 29 мая 2018

Я бы использовал shared_mutex и использовал бы:

  • общую блокировку во всех потоках, которые просто читают вектор (при чтении вектора)
  • уникальная блокировка в этой теме, когдаизменение размера вектора

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

Блокировкаавтоматически подразумевает барьер памяти, иначе блокировка не будет иметь большого смысла.

...