2017-01-20 4 views
3

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

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

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

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

Если «повозка, запряженная волами» будет использоваться, будет эта работа для разблокировки все ожидающих потоков на состояние и может быть вызвана из любой (в потоке) нитей:

//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... 
    } 
} 

Edit:

ворота класс

#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 

ответ

7

Переменные условия будить вещи поддельно.

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

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

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

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

Запрет крайних случаев, вы обычно хотите использовать лямбда-версию 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(); 
    } 
+0

# 1 Так что мьютекс предназначен исключительно для защиты доступа к самому 'std :: condition_variable'? # 2 Поэтому мне всегда приходится приобретать мьютекс из сигнальных потоков, прежде чем устанавливать условие boolean (которое теперь не должно быть атомарным) И отправка события уведомления, чтобы гарантировать, что уведомление проходит в целом? # 3 Могу ли я сохранить версию while (! BoolVar)? Я не понял, почему мне нужно использовать более сложную форму с двумя аргументами, извините. –

+0

@JustinBieber Это потому, что вы певец, а не программист. Форма 2-arg проста и эквивалентна; без контроля потока, просто тестовая функция. Мьютекс защищает доступ к переменной условия * и * тестируемому значению. Существует узкое условие гонки, в котором не существует мьютекса в любой точке между настройкой bool и отправкой уведомления, при котором потоки, начинающие ждать, никогда не проснутся. – Yakk

+0

Не могли бы вы взглянуть на класс, который я написал как сообщение, и подтвердить, что он безопасен в использовании? Мне очень жаль перетаскивать это, но мне тяжело обертывать вокруг себя голову. Это тяжело для нас, певцов. –

3

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

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

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

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

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

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

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

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

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

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

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

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

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

Смежные вопросы