7
votes

Approche de l'utilisation d'une STD :: Atomic par rapport à STD :: Condition_Variable WRT Pause & Reprenant un filet STD :: Thread en C ++

Ceci est une question distincte mais liée à la question précédente que j'ai posée ICI

J'utilise un std :: thread dans mon C ++ code pour constamment sonder pour certaines données et ajouter à un tampon. J'utilise un C ++ lambda pour démarrer le thread comme suit: xxx

thread_running est un atomic déclaré en en-tête de classe. Voici mon getdata Fonction: xxx

Suivant I Date d'ajournée fonction fonction où je définis thread_running < / code> sur false de sorte qu'il sort de la boucle tandis que dans le bloc lambda . xxx

Si je comprends, je peux mettre en pause et reprendre Le fil à l'aide d'un std :: Condition_variable Comme indiqué sur Voici dans ma question précédente.

mais y a-t-il un désavantage si je viens d'utiliser le std :: atomic thread_running Pour exécuter ou non exécuter la logique dans getdata () Comme ci-dessous? xxx

va-t-il brûler plus de cycles de processeur comparés à l'approche de l'utilisation d'un STD :: Condition_Variable Comme décrit Ici ?


7 commentaires

Honnêtement, je ne comprends pas the_thread.join (); qui est immédiatement après thread_running = false . Je pense que régler thread_running à false , rend le fichier de la boucle de threads pour quitter, suivi de la sortie du fil. Que signifie .join dans ce cas? Surtout, lorsque le fil utilise boucle .


@Nawaz the joindre appel est nécessaire car l'OP n'a pas détaché le fil. S'il ne rejoint pas le fil mort sur le fil principal, la ou les ressources du fil sous-jacent seront divulguées (finalement éventuellement en collision de l'application).


@Nawaz the_thread.join () est requis à partir du fil principal pour attendre que le thread du travailleur s'arrête, quelle est sortie de getdata () boucle. Si je ne fais pas un .join , je fais face aux collisions d'applications indéterminées. Par conséquent, il est toujours bon d'avoir même bien que cela ne soit pas entièrement obligatoire. Mais ma question est différente ici comme vous le voyez. Je ne suis pas préoccupé par le .join


@MarkB: Je ne savais pas que le comportement de .join . Je pensais que seulement bloque l'appel, jusqu'à ce que le fil se termine. Même la CPPRAGENCE ne mentionne pas ce que vous avez dit sur la fuite des ressources et le crash!


@Nawaz Permettez-moi de retourner ma déclaration un peu: cela fonctionne de cette façon sur la variété de systèmes Solaris / Linux que j'ai travaillé, mais Windows peut le gérer plus gracieusement.


@Nawaz: "Même la CPPRAGENCE ne mentionne pas ce que vous avez dit" Sure qu'il fait ! Ceci est un comportement de base et mandaté standard. Rejoignez vos discussions!


Avec la stratégie d'utilisation d'un atomique, votre thread n'est pas pausé du tout . Il va brûler un noyau de CPU en vérifiant constamment si ce booléen est vrai ou faux encore et encore et encore à une vitesse de 4 GHz. Vous voulez la condition_variable, pas de questions.


3 Réponses :


1
votes

sauf si vous manquez quelque chose, vous avez déjà répondu à cela dans votre question initiale: vous créerez et détruisez le fil de travailleur à chaque fois qu'il est nécessaire. Cela peut ou non être un problème dans votre application réelle.


3 commentaires

Dans ma question initiale, terminer l'arrêt du fil et le démarrage d'un nouveau est vraiment un problème. Je pouvais même voir le décalage lorsque j'appuie plusieurs fois le bouton UI pour jouer / mettre en pause le fil de travail. C'est la raison principale que je suis pour les alternatives de ne pas arrêter le fil. De plus, j'ai lu que le nettoyage d'un fil et de démarrer un nouveau système d'exploitation a besoin d'allouer des ressources pour le fil qui est une lourde affaire. À droite ?


Vous avez vu le retardé en raison de rejoindre tous les fils engendains. Un seul fil de fil ne serait pas perceptible


@ user1095108 convenir des machines de bureau WRT. Mon code est également utilisé sur les mobiles et le matériel de faible puissance. Et sur un téléphone d'android de milieu de gamme, si je appuyez spécifiquement sur le bouton de lecture en pause de plusieurs fois, ce thread entraîne le démarrage et l'arrêt, puis un léger décalage s'affiche une fois dans une quinzaine d'env.



1
votes

Il y a deux problèmes différents résolus et cela peut dépendre de ce que vous faites réellement. Un problème est "Je veux que mon fil coule jusqu'à ce que je le dise de s'arrêter." L'autre semble être un cas de "J'ai une paire de producteurs / consommateurs et souhaitez pouvoir en informer le consommateur lorsque des données sont prêtes." Le thread_running et jointure La méthode fonctionne bien pour le premier de ceux-ci. La seconde, vous voudrez peut-être utiliser un mutex et une condition car vous faites plus que simplement utiliser l'état pour déclencher des travaux. Supposons que vous ayez un vecteur . Vous gardez cela avec le mutex, la condition devient donc [& work] () {retour! Travail.empty (); } ou quelque chose de similaire. Lorsque l'attente revient, vous détenez le mutex afin que vous puissiez prendre les choses du travail et les faire. Lorsque vous avez terminé, vous revenez à attendre, libérant le mutex afin que le producteur puisse ajouter des choses à la file d'attente.

Vous voudrez peut-être combiner ces techniques. Avoir un "traitement fait" atomique que tous vos threads vérifient périodiquement à savoir quand sortir de la sortie afin de pouvoir les rejoindre. Utilisez la condition pour couvrir le cas de la livraison de données entre les threads.


8 commentaires

Mon besoin n'est pas aussi complexe que le scénario que vous avez expliqué. Mon besoin est seulement de jouer ou de mettre en pause le fil. C'est ça. Par conséquent, je pense seulement un std :: atomique variable thread_running suffirait à exécuter la logique lourde dans getdata () ou n'exécute pas la logique dans getdata ()


Mon problème est la main n'est pas un problème de consommateur de producteurs. Il est juste autonome travailleur_thread qui doit être en pause et repris fréquemment. Pensez-vous que j'ai encore besoin d'utiliser un std :: condition_variable pour y parvenir?


Vous avez besoin de l'attente () ou du thread va brûler inutilement des cycles de CPU


Aucun d'entre eux n'est vraiment le meilleur choix pour la résume de pause. La condition est probablement moins intense en ressources en général. Beaucoup de mutexieurs implémentations ont une sorte de file d'attente en attente de sorte que lorsque vous dites «attendez que cela soit vrai, puis ré-acquérir le mutex« Ce que vous dites vraiment, c'est que quelqu'un publie le mutex, l'acquérir, vérifiez mon état. et retourne vrai si la condition passe, sinon libère le mutex et réessaye ". Vous devez vous assurer que chaque fois que vous changez le drapeau, vous le faites tout en maintenant le mutex, mais ...


@ user1095108 Voici ce que je voulais confirmer. Si je brûle des cycles CPU, j'utiliserai oui, je vais utiliser std :: Condition_Variable & attendre () .


@Charlie Je dois dire que C ++ 's std :: atomique est assez solide. Si j'utilise une variable atomique pour vérifier thread_running indique, alors je n'ai pas besoin de le protéger en utilisant un std :: mutex . J'ai testé cette chose sur OSX, iOS & Android en tapotant sauvagement sur le bouton Play / Pause de mon UI de l'application. Lorsque vous utilisez std :: atomic Vous n'avez pas besoin mutex protection. En dehors de cela, comme vous le dites, condition_variable est moins intensif de ressources. Donc, je vais utiliser la même chose pour mon code. Merci pour cette clarification.


@Nelsonp Ce n'est pas que Atomic sera faux, voire que cela ne fonctionnera pas. Le chèque de travail serait assez rapide à chaque fois par la boucle, mais avoir un fil qui sort et doit être révisé contre un fil qui attend qu'une notification est probablement une chose plus grande à considérer. PENDANT (TRUE) {SI (SIO_ATOMIC_BOOL) {...}}}} brûlera essentiellement un noyau sur ce fil lorsque la condition est fausse. tandis que (vrai) {quelqu'un_condition.wait (...); ... Faire du travail ...}} dans un système bien implémenté doit placer le thread de veille et ne le réveille que lorsque la condition pourrait avoir changé.


@Charlie d'accord. Merci pour l'explication. J'ai accepté votre réponse



4
votes

Une variable de condition est utile lorsque vous souhaitez arrêter de conditionnellement un autre fil ou non. Donc, vous pourriez avoir un fil "travailleur" toujours en cours qui attend quand il remarque qu'il n'a rien à faire pour être en cours d'exécution.

La solution atomique nécessite votre interaction interaction de l'interface utilisateur synchronisée avec le fil de travailleur ou une logique très complexe pour le faire de manière asynchrone . P>

en règle générale, votre thread de réponse de l'interface utilisateur ne doit jamais bloquer sur l'état non prête des threads de travailleur. P>

struct worker_thread {
  worker_thread( std::function<void()> t, bool play = true ):
    task(std::move(t)),
    execute(play)
  {
    thread = std::async( std::launch::async, [this]{
      work();
    });
  }
  // move is not safe.  If you need this movable,
  // use unique_ptr<worker_thread>.
  worker_thread(worker_thread&& )=delete;
  ~worker_thread() {
    if (!exit) finalize();
    wait();
  }
  void finalize() {
    auto l = lock();
    exit = true;
    cv.notify_one();
  }
  void pause() {
    auto l = lock();
    execute = false;
  }
  void play() {
    auto l = lock();
    execute = true;
    cv.notify_one();
  }
  void wait() {
    Assert(exit);
    if (thread)
      thread.get();
  }
private:
  void work() {
    while(true) {
      bool done = false;
      {
        auto l = lock();
        cv.wait( l, [&]{
          return exit || execute;
        });
        done = exit; // have lock here
      }
      if (done) break;
      task();
    }
  }
  std::unique_lock<std::mutex> lock() {
     return std::unique_lock<std::mutex>(m);
  }
  std::mutex m;
  std::condition_variable cv;
  bool exit = false;
  bool execute = true;
  std::function<void()> task;
  std::future<void> thread;
};


1 commentaires

Merci d'une telle réponse éclatante. Par conséquent, accepter cette réponse.