Domanda perché ho bisogno di std :: condition_variable?


l'ho trovato std::condition_variable è molto difficile da usare a causa di wakeups spurie. Quindi a volte ho bisogno di impostare un flag come:

atomic<bool> is_ready;

ho impostato is_ready a true prima chiamo notifica (notify_one() o notify_all()), e poi aspetto:

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

C'è qualche ragione per cui non dovrei fare questo: (Modifica: Ok, questa è davvero una cattiva idea).

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

E se condition_variable avevo scelto una durata di attesa (non so se questo è vero o no), preferisco sceglierlo io stesso.


44
2018-05-03 01:59


origine


risposte:


Puoi codificarlo in entrambi i modi:

  1. Utilizzo di atomics e un ciclo di polling.
  2. Usare un condition_variable.

L'ho codificato in entrambi i modi per te qui sotto. Sul mio sistema posso monitorare in tempo reale quanta CPU sta utilizzando un dato processo.

Prima con il ciclo di polling:

#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();
}

Per me questo richiede 30 secondi per l'esecuzione, e mentre l'esecuzione del processo richiede circa il 99,6% di una CPU.

In alternativa con a 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();
}

Questo ha lo stesso identico comportamento tranne che durante l'esecuzione di 30 secondi, il processo sta prendendo 0,0% di CPU. Se stai scrivendo un'app che potrebbe essere eseguita su un dispositivo alimentato a batteria, quest'ultimo è quasi infinitamente più facile sulla batteria.

Ora, ammettiamolo, se hai avuto un'implementazione molto scarsa di std::condition_variable, potrebbe avere la stessa inefficienza del ciclo di polling. Tuttavia, in pratica, un tale venditore dovrebbe uscire dal mercato abbastanza rapidamente.

Aggiornare

Per i sorrisi ho aumentato il mio ciclo di attesa variabile di condizione_ con un rilevatore di sveglia spuria. L'ho eseguito di nuovo, e non ha stampato nulla. Non una sveglia spuria. Questo ovviamente non è garantito. Ma dimostra cosa può raggiungere un'implementazione di qualità.


76
2018-05-03 02:21



Lo scopo di std::condition_variable è aspettare che alcune condizioni diventino realtà. È non progettato per essere solo un ricevitore di una notifica. È possibile utilizzarlo, ad esempio, quando un thread del consumatore deve attendere che una coda diventi non vuota.

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

Il consumatore (get_from_queue) è non aspettando la variabile di condizione, stanno aspettando la condizione the_queue.empty(). La variabile di condizione ti dà il modo di metterli a dormire mentre stanno aspettando, rilasciando simultaneamente il Mutex e facendo così in un modo che eviti le condizioni di gara dove ti manca il risveglio.

La condizione che stai aspettando dovrebbe essere protetta da un mutex (quello che rilasci quando aspetti sulla variabile condition). Ciò significa che la condizione raramente (se mai) deve essere atomic. Si accede sempre da un mutex.


28
2018-05-03 02:20