Использование std::conditional_variable для ожидания условия

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

1) Почему std::condition_variable::wait(...) снова блокирует мьютекс после того, как "уведомление" было отправлено, чтобы отключить его?

2) Видя поведение в "1)", означает ли это, что когда вы делаете std::condition_variable::notify_all это только делает так, чтобы все ожидающие потоки были разблокированы / разбужены... но по порядку вместо всех сразу? Если так, что можно сделать, чтобы сделать все это сразу?

3) Если я забочусь только о потоках, спящих до тех пор, пока не будет выполнено условие, и не забочусь ни одного бита о получении мьютекса, что я могу сделать? Есть ли альтернатива или должна нынешняя std::condition_variable::wait(...) подход (ы) быть взломан вокруг этого?

Если используется "hackery", будет ли эта функция работать для разблокировки всех ожидающих потоков в условии и может ли она вызываться из любого (для потока) потока:

//declared somehwere and modified before sending "notify"(ies)
std::atomic<bool> global_shared_condition_atomic_bool;

//the single(for simplicity in our case) condition variable matched with the above boolean result
std::condition_variable global_shared_condition_variable;

static void MyClass:wait()
{
    std::mutex mutex;
    std::unique_lock<std::mutex> lock(mutex);

    while (!global_shared_condition_atomic_bool) global_shared_condition_variable.wait(lock);
}

он был бы вызван из случайных "ожидающих" потоков, например так:

void random_thread_run()
{
    while(someLoopControlValue)
    {
        //random code...
        MyClass:wait(); //wait for whatever condition the class+method is for.
        //more random code...
    }
}

Редактировать:

Класс ворот

#ifndef Gate_Header
#define Gate_Header

#include <mutex>
#include <condition_variable>

class Gate
{
public:
    Gate()
    {
        gate_open = false;
    }

    void open()
    {
        m.lock();
        gate_open = true;
        m.unlock();

        cv.notify_all();
    }

    void wait()
    {
        std::unique_lock<std::mutex> lock(m);

        while (!gate_open) cv.wait(lock);
    }

    void close()
    {
        m.lock();
        gate_open = false;
        m.unlock();
    }

private:
    std::mutex m;
    std::condition_variable cv;
    bool gate_open;
};

#endif

2 ответа

Решение

Переменные состояния пробуждают вещи внезапно.

У вас должен быть мьютекс, и он должен защищать какое-то сообщение, чтобы они работали, или у вас нет нулевой гарантии, что любое такое пробуждение произошло.

Это было сделано, по-видимому, потому что эффективные реализации не поддельной версии в конечном итоге будут реализованы в терминах такой ложной версии.

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

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

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

Аудит кода переменной условия невозможен, если вы не выполните аудит как кода уведомления, так и кода ожидания.

struct gate {
  bool gate_open = false;
  mutable std::condition_variable cv;
  mutable std::mutex m;

  void open_gate() {
    std::unique_lock<std::mutex> lock(m);
    gate_open=true;
    cv.notify_all();
  }
  void wait_at_gate() const {
    std::unique_lock<std::mutex> lock(m);
    cv.wait( lock, [this]{ return gate_open; } );
  }
};

или же

  void open_gate() {
    {
      std::unique_lock<std::mutex> lock(m);
      gate_open=true;
    }
    cv.notify_all();
  }

Нет, ваш код не будет работать.

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

Основная причина всего этого mutex Это связано с концепцией ложного пробуждения, неудачным аспектом реализации ОС условных переменных. Потоки, ожидающие их, иногда просто начинают работать, хотя условие еще не выполнено.

mutexсвязанная проверка фактической переменной позволяет потоку проверить, был ли он внезапно пробужден или нет.

wait атомно выпускает mutex и начинает ждать на условии. когда wait выходы mutex атомарно повторно приобретен как часть процесса пробуждения. Теперь рассмотрим гонку между ложным пробуждением и уведомляющим потоком. Поток уведомлений может находиться в одном из 2 состояний: собирается изменить переменную или после ее изменения и собирается уведомить всех о пробуждении.

Если ложное пробуждение происходит, когда уведомляющий поток собирается изменить varaible, то один из них доберется до mutex первый. Таким образом, внезапно пробужденный поток увидит либо старое, либо новое значение. Если он увидит новое, то получит уведомление и пойдет заниматься своим делом. Если он видит старое, то он снова будет ждать состояния. Но если он увидел старый, то заблокировал уведомляющий поток от изменения этой переменной, поэтому ему пришлось ждать, пока ложный поток снова не перейдет в спящий режим.

Почему std::condition_variable::wait(...) снова блокирует мьютекс после того, как было отправлено уведомление, чтобы отменить его?

Поскольку mutex блокирует доступ к условной переменной. И первое, что вы должны сделать после пробуждения от wait вызов должен проверить условную переменную. Таким образом, это должно быть сделано под защитой mutex,

Поток сигнализации не должен изменять переменную, пока другие потоки читают ее. Вот что mutex для.

Видя поведение в "1)", означает ли это, что когда вы делаете std::condition_variable::notify_all, это только делает так, чтобы все ожидающие потоки были разблокированы / разбужены... но по порядку вместо всех сразу?

Порядок, в котором они просыпаются, не указан. Однако к тому времени notify_all возвращается, все темы гарантированно разблокированы.

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

Ничего такого. condition_variable требует, чтобы доступ к фактической переменной, которую вы проверяете, контролировался через mutex,

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