Какой правильный способ борьбы с ложными пробуждениями, в целом? - PullRequest
0 голосов
/ 05 сентября 2018

Есть ли среди приведенных ниже вариантов правильный способ борьбы с ложными пробуждениями при использовании условных переменных?

1) Поместите wait(unique_lock_ul) в бесконечный цикл while, используя логическое значение

unique_lock<mutex> ul(m); 
while(!full)
  cv.wait(ul);

2) То же самое, если

unique_lock<mutex> ul(m); 
if(!full)
  cv.wait(ul);

3) Поместите условие в wait(), например, используя лямбда-функции

unique_lock<mutex> ul(m); 
cv.wait(ul, [&](){return !full;});

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

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

Ответы [ 2 ]

0 голосов
/ 05 сентября 2018

Любой из 1 или 3 способов подходит для борьбы с ложными пробуждениями (при условии, что модификация full защищена тем же мьютексом), за исключением случаев, когда условие предиката неверно, оно должно быть:

unique_lock<mutex> ul(m); 
cv.wait(ul, [&](){return full;});

чтобы сделать этот код равным варианту 1.

Вариант 2 не подходит, хотя при ложном пробуждении условие ожидания не будет перепроверено, в отличие от 2 других случаев.

0 голосов
/ 05 сентября 2018

Короткий ответ: ваш код может быть правильным или неправильным; Вы не показали, как именно манипулируют full.

Отдельные биты кода C ++ никогда не являются поточно-ориентированными. Потокобезопасность является реляционным свойством кода; два бита кода могут быть потокобезопасными по отношению друг к другу, если они никогда не могут вызвать состояние гонки.

Но один бит кода не является поточно-ориентированным; сказать что-то потокобезопасно - это все равно, что сказать что-то "такой же высоты".


Шаблон переменной условия "monkey see monkey do" таков:

template<class T>
class cv_bundle {
  std::mutex m;
  T payload;
  std::condition_variable cv;
public:
  explicit cv_bundle( T in ):payload(std::move(in)) {}

  template<class Test, class Extract>
  auto wait( Test&& test, Extract&& extract ) {
    std::unique_lock<std::mutex> l(m);
    cv.wait( l, [&]{ return test(payload); } );
    return extract(payload);
  }
  template<class Setter>
  void load( Setter&& setter, bool only_one = true ) {
    bool is_set = false;
    {
      std::unique_lock<std::mutex> l(m);
      is_set = setter( payload );
    }
    if (!is_set) return; // nothing to notify
    if (only_one)
      cv.notify_one();
    else
      cv.notify_all();
  }
};

test принимает T& payload и возвращает истину, если есть что потреблять (т. Е. Пробуждение не является ложным).

extract принимает T& payload и возвращает любую информацию, которую вы хотите от него. Обычно он сбрасывает полезную нагрузку.

setter изменяет T& payload таким образом, что test возвращает true. Если это так, он возвращает true. Если он решит не делать, он вернет false.

Все 3 вызываются в мьютекс блокирующем доступ к T payload.

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

Хотя я соединил эти 3 вещи вместе, вы можете использовать один мьютекс для набора переменных условий или использовать мьютекс для большего, чем просто переменную условия. Полезной нагрузкой может быть логическое значение, счетчик, вектор данных или нечто более чуждое; в общем, он всегда должен быть защищен мьютексом. Если оно атомарное, в какой-то момент в открытом интервале между изменяемым значением и уведомлением мьютекс должен быть заблокирован, иначе вы рискуете потерять уведомление.

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

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

Обратите внимание, что если любая из лямбд прошла в go и перезвонила в cv_bundle, код, который я показал, больше не действителен.

...