Два 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;
        }
    }

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();
}

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

Другие вопросы по тегам