зачем условной переменной нужен мьютекс

  1. зачем блокировать мьютекс с помощью unique_lock перед тем как вызывать метод wait у условной переменной?
  2. зачем в качестве параметра методу wait нужен мьютекс, ведь если бы я хотел я бы и сам блокировал нужный мьютекс, после пробуждения?
  3. нужно ли блокировать тот же самый мьютекс перед вызовом notify_one для условной переменной в другом потоке?

немного контекста

#include <iostream>

#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex outputStreamMutex;

std::mutex productMutex;
std::condition_variable condVar;
bool productReady = false;

class Product 
{
public:
  void make() {}
} product;


void makeProduct()
{
  {
    std::lock_guard<std::mutex> lock(outputStreamMutex);
    std::cout << "making process...\n";
  }

  {
    std::lock_guard lock(productMutex);
    product.make();
    productReady = true;
    condVar.notify_one();

    std::lock_guard<std::mutex> lockStream(outputStreamMutex);
    std::cout << "product is made\n";
  }
}

void send(const Product& prod)
{
  //...
}

void sendProduct()
{
  {
    std::lock_guard<std::mutex> lock(outputStreamMutex);
    std::cout << "waiting product process...\n";
  }

  {
    std::unique_lock lock(productMutex);
    condVar.wait(lock, []() {return productReady;});
    send(product);
    productReady = false;

    std::lock_guard<std::mutex> lockStream(outputStreamMutex);
    std::cout << "product is sent\n";
  }
}


int main()
{
  std::thread makerThread(makeProduct);
  std::thread senderThread(sendProduct);
  makerThread.join();
  senderThread.join();

  return 0;
}

Ответы (1 шт):

Автор решения: Fat-Zer

Типовое использование условной переменной без плюсового сахара обычно выглядит как-то так (псевдокод)¹:

condition_variable cv; // условная переменная
mutex dataMutex;       // Мьютекс, контролирующий доступ к данным
MyData data;           // Некоторые пользовательские данные

Consumer() {
  dataMutex.lock();        // Захват мьютекса, дабы данные не изменялись во 
                           // время проверки предиката
  while(!data.isReady()) { // Проверка, не готовы ли данные
    cv.wait(dataMutex);    // Ожидание сигнала от производителя, когда он изменит 
  }                        // данные

  // ... какая-то обработка данных ...

  dataMutex.unlock();      // Освобождение мьютекса т.к. он более не используется
}

Produser() {
  while (1) {
    dataMutex.lock();        // Захват мьютекса, чтобы изменить данные
    data.doWork();           // Какая-то обработка данных
    cv.notify_one();         // Пробуждение одного потока, ждущего в очереди
    dataMutex.unlock();      // Освобождение мьютекса, дабы потребитель смог
  }                          // проверить своё услови и обработать данные
}

Типовая реализация условной переменной в самом примитивном случаеустроена как-то так(также псевдокод):

struct condition_variable {
  mutex cvMut;                     // Внутренний мьютекс защищающий доступ к очереди.
  queue<thread_t*> waitingQueue;   // Очередь потоков, ожидающих сигнала
  
  void wait(mutex dataMutex) {
    // Добавление текущего потока в очередь ожидания
    cvMut.lock();
    waitingQueue.push(this_thread);
    cvMut.unlock();
    
    dataMutex.unlock(); // Освобождение мьютекса доступа к данным,
                        // дабы производитель мог с ними работать
    suspend();          // Приостановка работы и ожидание сигнала о пробуждении

    dataMutex.lock();   // Захват Мьютекса обратно, дабы внешняя программа смогла
                        // проверить, выполнено ли условие.
  }

  void notify_one(){
    // Получить процесс из очереди ожидания
    cvMut.lock();
    thread_t *thread = waitingQueue.pop();
    cvMut.unlock();
     
    // Если очередь не пуста, то разбудить ожидающий поток.
    if(thread) {
      restart(thread);
    }
  }
}

Реализация suspend() и resume() системно-зависимы. Например, в классических UNIX-системах они могут строится на сигналах с помощью sigsuspend()/kill(), а в современном (2.4+) GNU/Linux они строятся на futex'ах.


  1. зачем блокировать мьютекс с помощью unique_lock перед тем как вызывать метод wait у условной переменной?

Как видно из типовой реализации выше, блокировка должно удерживаться от проверки условия и до добавления потока в очередь ожидания. Если бы её не было, то из-за состояния гонки была бы возможна такая последовательность событий:

Потребитель   : проверяет условие.
Производитель : изменяет данные
Производитель : проверяет, что очередь ожидания пуста и ничего не делает
Потребитель   : добавляет себя в очередь
Потребитель   : засыпает и ждёт сигнала

В итоге потребитель может остаться спать неопределённо долго, хотя условие уже выполнено. Блокировка собственно не даёт Производителю менять данные между проверкой и добавлением себя в очередь.

Использование именно unique_lock, а не std::lock_guard обусловлено тем, что последний не может быть разблокирован. Можно было бы обойтись и обычным std::mutex::lock()/unlock(), но в С++ это чревато висящей блокировкой, если кто-то бросит исключение (см. общую мотивацию к использованию объектов-блокировок).

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

см. типовую реализацию и ответ на (1). Важно блокировать его во время проверки условия и до добавления потока в очередь ожидания включительно.

  1. нужно ли блокировать тот же самый мьютекс перед вызовом notify_one для условной переменной в другом потоке?

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

Если на условной переменной ожидает не более одного процесса, то абсолютно никакой разницы нет. Но, как предупреждает POSIX, если необходимо «предсказуемое поведение плланировщика» при наличии нескольких потребителей, то его следует одерживать т.к. в таком случае возможна ситуация голодания в том числе и более приоритетного потока. см. подробности здесь.


¹ Здесь и далее барьеры памяти не указаны

→ Ссылка