Два std :: unique_lock, используемые в одном мьютексе, вызывают тупик?

Я нашел этот код при обмене стеками обзора кода, который реализует проблема производитель-потребитель. Я публикую здесь фрагмент кода.

В данном коде давайте рассмотрим сценарий, когда производитель производит значение, вызывая void add(int num), он получает блокировку мьютекса mu и buffer.size()==size_, это заставляет производителя перейти в очередь ожидания из-за условной переменной cond.

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

Где я здесь ошибаюсь? Поскольку код, кажется, работает правильно, когда я его запускаю, отладка мне не помогла.

Спасибо

void add(int num) {
        while (true) {
            std::unique_lock<std::mutex> locker(mu);
            cond.wait(locker, [this](){return buffer_.size() < size_;});
            buffer_.push_back(num);
            locker.unlock();
            cond.notify_all();
            return;
        }
    }
    int remove() {
        while (true)
        {
            std::unique_lock<std::mutex> locker(mu);
            cond.wait(locker, [this](){return buffer_.size() > 0;});
            int back = buffer_.back();
            buffer_.pop_back(); 
            locker.unlock();
            cond.notify_all();
            return back;
        }
    }

person Sujith    schedule 06.10.2017    source источник
comment
wait должен освободить блокировку до тех пор, пока условие не будет выполнено, и повторно захватить его перед выходом wait   -  person Artemy Vysotsky    schedule 06.10.2017
comment
_1 _... зачем использовать цикл?   -  person Jarod42    schedule 06.10.2017


Ответы (2)


Ответ OutOfBound хорош, но немного подробнее о том, что именно «атомарно», полезно.

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

Форма условного критического раздела проверяет предикат внутренне. Однако это по-прежнему зависит от того, правильно ли сделаны уведомления.

В некотором смысле можно думать о wait как о том, что он делает следующее:

while (!predicate()) {
    mutex.unlock();
    /* sleep for a short time or spin */
    mutex.lock();
}

Переменная условия с уведомлениями позволяет прокомментированной строке в середине быть эффективной. Который дает:

while (!predicate()) {
    atomic { /* This is the key part. */
        mutex.unlock();
        sleep_until_notified();
    }
    mutex.lock();
}
person Zalman Stern    schedule 06.10.2017

Идея std::condition_variable::wait(lock, predicate) заключается в том, что вы ждете, пока не будет выполнен предикат, и после этого блокируете мьютекс. Чтобы сделать это атомарно (что важно в большинстве случаев), вы должны сначала заблокировать мьютекс, затем ожидание освободит его и заблокирует для проверки предиката. Если он выполняется, мьютекс остается заблокированным, и выполнение продолжается. В противном случае мьютекс снова будет освобожден.

person OutOfBound    schedule 06.10.2017