web-dev-qa-db-fra.com

Comment réveiller un std :: thread pendant qu'il dort

J'utilise C++ 11 et j'ai un std::thread qui est un membre de la classe et envoie des informations aux écouteurs toutes les 2 minutes. Autre que ça dort juste. Donc, je l'ai fait dormir pendant 2 minutes, puis envoyer les informations requises, puis dormir encore 2 minutes.

// MyClass.hpp
class MyClass {

    ~MyClass();
    RunMyThread();

private:
    std::thread my_thread;
    std::atomic<bool> m_running;
}


MyClass::RunMyThread() {

    my_thread = std::thread { [this, m_running] {
    m_running = true;
    while(m_running) {
        std::this_thread::sleep_for(std::chrono::minutes(2));
        SendStatusInfo(some_info);
    }
}};
}

// Destructor
~MyClass::MyClass() {
    m_running = false; // this wont work as the thread is sleeping. How to exit thread here?
}

Problème:
Le problème avec cette approche est que je ne peux pas quitter le fil pendant son sommeil. Je comprends à la lecture que je peux le réveiller avec un std::condition_variable et sortir en douceur? Mais je me bats pour trouver un exemple simple qui fait le strict minimum requis dans le scénario ci-dessus. Tous les exemples de condition_variable que j'ai trouvés semblent trop complexes pour ce que j'essaie de faire ici.

Question:
Comment puis-je utiliser un std::condition_variable pour réactiver le fil et quitter normalement pendant qu'il dort? Ou existe-t-il d'autres moyens d'atteindre le même objectif sans la technique condition_variable?

De plus, je vois que je dois utiliser un std::mutex conjointement avec std::condition_variable? Est-ce vraiment nécessaire? N’est-il pas possible d’atteindre cet objectif en ajoutant la logique std::condition_variable uniquement aux emplacements requis dans le code?

Environnement:
Linux et Unix avec les compilateurs gcc et clang.

14
Game_Of_Threads

Un exemple de travail pour vous en utilisant std::condition_variable:

struct MyClass {
    MyClass()
        : my_thread([this]() { this->thread(); })
    {}

    ~MyClass() {
        {
            std::lock_guard<std::mutex> l(m_);
            stop_ = true;
        }
        c_.notify_one();
        my_thread.join();
    }

    void thread() {
        while(this->wait_for(std::chrono::minutes(2)))
            SendStatusInfo(some_info);
    }

    // Returns false if stop_ == true.
    template<class Duration>
    bool wait_for(Duration duration) {
        std::unique_lock<std::mutex> l(m_);
        return !c_.wait_for(l, duration, [this]() { return stop_; });
    }

    std::condition_variable c_;
    std::mutex m_;
    bool stop_ = false;
    std::thread my_thread;
};
5
Maxim Egorushkin

Comment puis-je utiliser un std::condition_variable pour réactiver le fil et sortir en douceur pendant qu'il dormait? Ou existe-t-il d'autres moyens de réaliser la même chose sans la technique condition_variable?

Non, pas en C++ standard à partir de C++ 17 (il existe bien sûr des méthodes non standard, spécifiques à la plate-forme, et il est probable qu'une sorte de sémaphore sera ajouté à C++ 2a).

De plus, je vois que je dois utiliser un std::mutex conjointement avec std::condition_variable? Est-ce vraiment nécessaire?

Oui.

N'est-il pas possible d'atteindre l'objectif en ajoutant la logique std::condition_variable uniquement aux emplacements requis dans la pièce de code ici?

Non. Pour commencer, vous ne pouvez pas attendre un condition_variable sans verrouiller un mutex (et transmettre l'objet de verrouillage à la fonction wait). Vous devez donc toujours avoir un mutex. De toute façon, étant donné que vous devez avoir un mutex, obliger le serveur et l'auteur de la notification à utiliser ce mutex n'est pas si grave.

Les variables de condition sont soumises à des "réveils parasites", ce qui signifie qu'elles peuvent arrêter d'attendre sans raison. Pour savoir si elle s'est réveillée parce qu'elle a été notifiée ou si elle a été éveillée de manière anormale, vous avez besoin d'une variable d'état définie par le thread notifiant et lue par le thread en attente. Comme cette variable est partagée par plusieurs threads, le mutex garantit son accès en toute sécurité.

Même si vous utilisez une variable atomique pour la variable de partage, vous avez toujours besoin d'un mutex pour éviter les notifications manquées.

Tout cela est expliqué plus en détail dans https://github.com/isocpp/CppCoreGuidelines/issues/554

15
Jonathan Wakely

Comment utiliser une variable std :: condition_variable pour réactiver le fil et sortir en douceur pendant son sommeil?

Vous utilisez std::condition_variable::wait_for() au lieu de std::this_thread::sleep_for() et le premier peut être interrompu par std::condition_variable::notify_one() ou std::condition_variable::notify_all()

De plus, je vois que j'ai besoin d'utiliser un std :: mutex avec std :: condition_variable? Est-ce vraiment nécessaire? N'est-il pas possible d'atteindre l'objectif en ajoutant la logique std :: condition_variable uniquement aux emplacements requis dans la pièce de code ici?

Oui, il est nécessaire d’utiliser std::mutex avec std::condition_variable et vous devriez l’utiliser au lieu de créer votre indicateur std::atomic car, malgré son caractère atomique, vous auriez une condition de concurrence critique dans votre code et vous remarquerez que votre thread en veille manque parfois de notification si vous ne le faites pas. utilisez mutex ici.

8
Slava

Il y a un fait triste, mais vrai - ce que vous recherchez est un signal et les threads Posix n'ont pas de véritable mécanisme de signalisation.

De plus, la seule primitive de threading Posix associée à une sorte de synchronisation est une variable conditionnelle. C’est pourquoi votre recherche en ligne vous y conduit. Etant donné que le modèle de threading C++ est fortement basé sur l’API Posix, les primitives standard compatibles C++ Posix obtenir.

Sauf si vous êtes prêt à sortir de Posix (vous n'indiquez pas de plate-forme, mais il existe des méthodes natives pour manipuler des événements libres de ces limitations, notamment eventfd dans Linux), vous devrez vous en tenir à des variables de condition et, oui, travailler. with variable de condition nécessite un mutex, car il est intégré à l'API.

Votre question ne demande pas spécifiquement un exemple de code, je ne vous en fournis donc aucun. Faites-moi savoir si vous en voulez.

5
SergeyA

De plus, je vois que j'ai besoin d'utiliser un std :: mutex avec std :: condition_variable? Est-ce vraiment nécessaire? N'est-il pas possible d'atteindre l'objectif en ajoutant la logique std :: condition_variable uniquement aux emplacements requis dans la pièce de code ici?

std::condition_variable est une primitive de bas niveau. En réalité, son utilisation nécessite également de manipuler d'autres primitives de bas niveau.

struct timed_waiter {
  void interrupt() {
    auto l = lock();
    interrupted = true;
    cv.notify_all();
  }
  // returns false if interrupted
  template<class Rep, class Period>
  bool wait_for( std::chrono::duration<Rep, Period> how_long ) const {
    auto l = lock();
    return !cv.wait_until( l,
      std::chrono::steady_clock::now() + how_long,
      [&]{
        return !interrupted;
      }
    );
  }
private:
  std::unique_lock<std::mutex> lock() const {
    return std::unique_lock<std::mutex>(m);
  }
  mutable std::mutex m;
  mutable std::condition_variable cv;
  bool interrupted = false;
};

créez simplement un timed_waiter à un endroit où le ou les threads qui veulent attendre et le code qui veut interrompre peuvent le voir.

Les threads en attente font

while(m_timer.wait_for(std::chrono::minutes(2))) {
    SendStatusInfo(some_info);
}

pour interrompre faire m_timer.interrupt() (dis dans le dtor) puis my_thread.join() pour le laisser finir.

Exemple live :

struct MyClass {
    ~MyClass();
    void RunMyThread();
private:
    std::thread my_thread;
    timed_waiter m_timer;
};


void MyClass::RunMyThread() {

    my_thread = std::thread {
      [this] {
      while(m_timer.wait_for(std::chrono::seconds(2))) {
        std::cout << "SendStatusInfo(some_info)\n";
      }
    }};
}

// Destructor
MyClass::~MyClass() {
    std::cout << "~MyClass::MyClass\n";
    m_timer.interrupt();
    my_thread.join();
    std::cout << "~MyClass::MyClass done\n";
}

int main() {
    std::cout << "start of main\n";
    {
        MyClass x;
        x.RunMyThread();
        using namespace std::literals;
        std::this_thread::sleep_for(11s);
    }
    std::cout << "end of main\n";
}

Ou existe-t-il d'autres moyens d'obtenir le même résultat sans la technique condition_variable?

Vous pouvez utiliser std :: promise / std :: future comme alternative plus simple à un bool/condition_variable/mutex dans ce cas. Un future n'est pas sujet aux sillages parasites et ne nécessite pas un mutex pour la synchronisation.

Exemple de base:

std::promise<void> pr;
std::thread thr{[fut = pr.get_future()]{
    while(true)
    {
        if(fut.wait_for(2min) != future_status::timeout)
            return;
    }
}};
//When ready to stop
pr.set_value();
thr.join();
0
apophis42

Vous pouvez également utiliser promise/future pour ne pas avoir à vous soucier de conditionnel et/ou de threads:

#include <future>
#include <iostream>

struct MyClass {

    ~MyClass() {
        _stop.set_value();
    }

    MyClass() {
        auto future = std::shared_future<void>(_stop.get_future());
        _thread_handle = std::async(std::launch::async, [future] () {
            std::future_status status;
            do {
                status = future.wait_for(std::chrono::seconds(2));
                if (status == std::future_status::timeout) {
                    std::cout << "do periodic things\n";
                } else if (status == std::future_status::ready) {
                    std::cout << "exiting\n";
                }
            } while (status != std::future_status::ready);
        });
    }


private:
    std::promise<void> _stop;
    std::future<void> _thread_handle;
};


// Destructor
int main() {
    MyClass c;
    std::this_thread::sleep_for(std::chrono::seconds(9));
}
0
OznOg

Ou existe-t-il d'autres moyens d'obtenir la même chose sans technique variable à condition?

Une alternative à une variable condition est que vous pouvez réactiver votre thread à des intervalles beaucoup plus réguliers pour vérifier l'indicateur "en cours d'exécution" et se rendormir s'il n'est pas défini et que le temps imparti n'a pas encore expiré:

void periodically_call(std::atomic_bool& running, std::chrono::milliseconds wait_time)
{
    auto wake_up = std::chrono::steady_clock::now();

    while(running)
    {
        wake_up += wait_time; // next signal send time

        while(std::chrono::steady_clock::now() < wake_up)
        {
            if(!running)
                break;

            // sleep for just 1/10 sec (maximum)
            auto pre_wake_up = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);

            pre_wake_up = std::min(wake_up, pre_wake_up); // don't overshoot

            // keep going to sleep here until full time
            // has expired
            std::this_thread::sleep_until(pre_wake_up);
        }

        SendStatusInfo(some_info); // do the regular call
    }
}

Remarque: Vous pouvez définir le temps d'attente réel comme vous le souhaitez. Dans cet exemple je l'ai fait 100ms std::chrono::milliseconds(100). Cela dépend de la réactivité de votre thread à l’arrêt d’un signal.

Par exemple, dans une application, j'ai effectué cette opération pendant une seconde car j'étais content que mon application attende une seconde complète pour que tous les threads s'arrêtent avant d'être fermés à la sortie.

Votre réactivité dépend de votre demande. Plus le temps de réveil est court, plus CPU est consommé. Cependant, même des intervalles très courts de quelques millisecondes ne seront probablement pas enregistrés en termes de temps CPU.

0
Galik