Подтвердить что ты не робот

Зачем мне нужен std:: condition_variable?

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

atomic<bool> is_ready;

Я установил is_ready в true, прежде чем позвонить (notify_one() или notify_all()), а затем я жду:

some_condition_variable.wait(some_unique_lock, [&is_ready]{
    return bool(is_ready);
});

Есть ли какая-то причина, по которой я не должен просто делать это: (Edit: Хорошо, это действительно плохая идея.)

while(!is_ready) {
    this_thread::wait_for(some_duration); //Edit: changed from this_thread::yield();
}

И если condition_variable выбрал продолжительность ожидания (я не знаю, правда ли это или нет), я предпочитаю сам выбирать.

4b9b3361

Ответ 1

Вы можете запрограммировать это так:

  • Использование атоматики и цикла опроса.
  • Использование condition_variable.

Я закодировал это для вас ниже. В моей системе я могу контролировать в режиме реального времени, сколько процессор использует данный процесс.

Сначала с петлей опроса:

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

std::atomic<bool> is_ready(false);

void
test()
{
    std::this_thread::sleep_for(std::chrono::seconds(30));
    is_ready.store(true);
}

int
main()
{
    std::thread t(test);
    while (!is_ready.load())
        std::this_thread::yield();
    t.join();
}

Для меня это занимает 30 секунд, и при выполнении процесса занимает около 99,6% от процессора.

Альтернативно с condition_variable:

#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

bool is_ready(false);
std::mutex m;
std::condition_variable cv;

void
test()
{
    std::this_thread::sleep_for(std::chrono::seconds(30));
    std::unique_lock<std::mutex> lk(m);
    is_ready = true;
    cv.notify_one();
}

int
main()
{
    std::thread t(test);
    std::unique_lock<std::mutex> lk(m);
    while (!is_ready)
    {
        cv.wait(lk);
        if (!is_ready)
            std::cout << "Spurious wake up!\n";
    }
    t.join();
}

Это то же самое поведение, за исключением того, что во время выполнения 30 секунд процесс принимает 0.0% CPU. Если вы пишете приложение, которое может выполняться на устройстве с батарейным питанием, последнее почти бесконечно легче на батарее.

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

Обновление

Для усмешек я увеличил свой цикл wait_variable wait с ложным детектором пробуждения. Я снова побежал, и ничего не распечатывал. Не одно побочное пробуждение. Это, конечно, не гарантируется. Но он демонстрирует, что может достичь качественная реализация.

Ответ 2

Цель std::condition_variable - подождать, пока какое-то условие станет истинным. Он не предназначен только для получателя уведомления. Вы можете использовать его, например, когда потребительский поток должен ждать, пока очередь станет непустой.

T get_from_queue() {
   std::unique_lock l(the_mutex);
   while (the_queue.empty()) {
     the_condition_variable.wait(l);
   }
   // the above loop is _exactly_ equivalent to the_condition_variable.wait(l, [&the_queue](){ return !the_queue.empty(); }
   // now we have the mutex and the invariant (that the_queue be non-empty) is true
   T retval = the_queue.top();
   the_queue.pop();
   return retval;
}

put_in_queue(T& v) {
  std::unique_lock l(the_mutex);
  the_queue.push(v);
  the_condition_variable.notify_one();  // the queue is non-empty now, so wake up one of the blocked consumers (if there is one) so they can retest.
}

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

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