это дает бессмысленный вывод и сегфоут.
Это будет все еще потенциально даст вам сегфо при текущемсхема синхронизации, даже если вы используете предложенную блокировку в стиле RAII , например:
void pop() {
std::lock_guard<std::mutex> lock{ mtx };
stk.pop_back();
}
int top() const {
std::lock_guard<std::mutex> lock{ mtx };
return stk[stk.size() - 1];
}
bool empty() const {
std::lock_guard<std::mutex> lock{ mtx };
return stk.size() == 0;
}
при , вы не заботитесь о условие гонки возникающих между двумя последующими вызовами этих методов разными потоками.Например, подумайте о том, что происходит, когда в стеке остался один элемент, и один поток спрашивает, пуст ли он, и получает ли false
, а затем у вас есть переключение контекста, а другой поток получает тот же false
для того же вопроса.Так что они оба гоняются за этим top()
и pop()
.В то время как первый уже выскакивает, а затем другой пытается top()
, он будет делать это в ситуации, когда stk.size() - 1
дает -1
.Следовательно, вы получаете segfault за попытку доступа к несуществующему отрицательному индексу стека: (
Я не знаю, где я делаю ошибку. Этоправильный способ написания поточно-безопасного стекового класса?
Нет, это не правильный путь, мьютекс только гарантирует, что другие потоки, блокирующие тот же мьютекс, не могут в данный момент выполнять этот же разделкод. Если они попадают в один и тот же раздел, им запрещается входить в него, пока мьютекс не будет освобожден. Но вы вообще не блокируетесь между вызовом до empty()
и остальными вызовами. Один поток получает empty()
, блокирует, получает значение, затем освобождает его, а затем другой поток может свободно вводить и запрашивать и вполне может получить то же значение. Что мешает позже ввести ваш вызов top()
и что мешает первому потоку быть уже после того же pop()
в это время?
В этих сценариях вам нужно быть осторожным, чтобы увидеть весь объем того, что нуждается в защитес точки зрения синхронности.То, что здесь сломано, называется атомарность , что означает свойство «быть не в состоянии разрезать посередине».Как вы можете видеть , здесь говорится , что " атомарность часто обеспечивается взаимным исключением, " - как при использовании мьютексов, как вы это сделали.Чего не хватало, так это того, что было слишком мелко слишком мелко - операция "размер атома" была слишком мала.Вы должны были защищать всю последовательность empty()
- top()
- pop()
в целом, так как теперь мы понимаем, что не можем отделить ни одну часть от трех.В коде это может выглядеть как вызов этого внутри func()
и печать в cout
, только если он вернет true
:
bool safe_pop(int& value)
{
std::lock_guard<std::mutex> lock{ mtx };
if (stk.size() > 0)
{
value = stk[stk.size() - 1];
stk.pop_back();
return true;
}
return false;
}
По общему признанию, это не оставляет много для параллельной работы здесь, но я думаю, это приличное упражнение в параллельности.