web-dev-qa-db-fra.com

pourquoi ai-je besoin de std :: condition_variable?

J'ai trouvé que std::condition_variable Est très difficile à utiliser en raison de réveils parasites. Donc, parfois, je dois définir des indicateurs tels que:

atomic<bool> is_ready;

J'ai défini is_ready Sur true avant d'appeler notify (notify_one() ou notify_all()), puis j'attends:

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

Y a-t-il une raison pour laquelle je ne devrais pas simplement faire ceci: (Edit: Ok, c'est vraiment une mauvaise idée.)

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

Et si condition_variable Avait choisi une durée d'attente (je ne sais pas si c'est vrai ou non), je préfère le choisir moi-même.

51
user955249

Vous pouvez coder de cette façon:

  1. Utilisation de l'atomique et d'une boucle d'interrogation.
  2. Utilisant un condition_variable.

Je l'ai codé dans les deux sens pour vous ci-dessous. Sur mon système, je peux surveiller en temps réel la quantité de CPU utilisée par un processus donné.

Tout d'abord avec la boucle d'interrogation:

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

Pour moi, cela prend 30 secondes à exécuter, et pendant l'exécution, le processus prend environ 99,6% d'un processeur.

Alternativement avec un 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();
}

Cela a exactement le même comportement, sauf que pendant l'exécution de 30 secondes, le processus prend 0,0% de processeur. Si vous écrivez une application qui pourrait s'exécuter sur un appareil alimenté par batterie, ce dernier est presque infiniment plus facile sur la batterie.

Maintenant, certes, si vous aviez une très mauvaise implémentation de std::condition_variable, il pourrait avoir la même inefficacité que la boucle d'interrogation. Cependant, dans la pratique, un tel fournisseur doit cesser ses activités assez rapidement.

Mise à jour

Pour les sourires, j'ai augmenté ma boucle d'attente condition_variable avec un détecteur de réveil parasite. Je l'ai relancé et il n'a rien imprimé. Pas un faux réveil. Ce n'est bien sûr pas garanti. Mais cela démontre ce qu'une mise en œuvre de qualité peut réaliser.

83
Howard Hinnant

Le but de std::condition_variable Est d'attendre qu'une condition devienne vraie. C'est pas conçu pour être juste un récepteur d'une notification. Vous pouvez l'utiliser, par exemple, lorsqu'un thread consommateur doit attendre qu'une file d'attente ne devienne pas vide.

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

Le consommateur (get_from_queue) Attend pas la variable de condition, il attend la condition the_queue.empty(). La variable de condition vous donne le moyen de les endormir pendant qu'ils attendent, libérant simultanément le mutex et le faisant d'une manière qui évite les conditions de course où vous manquez de réveils.

La condition que vous attendez doit être protégée par un mutex (celui que vous relâchez lorsque vous attendez sur la variable de condition.) Cela signifie que la condition doit rarement (voire jamais) être un atomic. Vous y accédez toujours depuis un mutex.

30
Wandering Logic