5 votes

Le thread C++11 ne fonctionne pas avec une fonction membre virtuelle

J'essaie de faire en sorte qu'une classe exécute un thread, qui appellera une fonction membre virtuelle nommée Tick() dans une boucle. Ensuite, j'ai essayé de dériver une classe et de surcharger la fonction base::Tick().

mais lors de l'exécution, le programme appelle simplement le Tick de la classe de base au lieu de le surcharger. une solution ?

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

using namespace std;

class Runnable {
 public:
  Runnable() : running_(ATOMIC_VAR_INIT(false)) {

   }
  ~Runnable() { 
    if (running_)
      thread_.join();
  }
  void Stop() { 
    if (std::atomic_exchange(&running_, false))
      thread_.join();
  }
  void Start() {
    if (!std::atomic_exchange(&running_, true)) {
      thread_ = std::thread(&Runnable::Thread, this);
    }
  }
  virtual void Tick() {
    cout << "parent" << endl;
  };
  std::atomic<bool> running_;

 private:
  std::thread thread_;
  static void Thread(Runnable *self) {
    while(self->running_) {
      self->Tick();
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
  }
};

class Fn : public Runnable {
 public:
  void Tick() {
    cout << "children" << endl;
  }
};

int main (int argc, char const* argv[])
{
  Fn fn;
  fn.Start();
  return 0;
}

sorties :

parent

12voto

David Schwartz Points 70129

Vous ne pouvez pas laisser un objet sortir de sa portée tant que vous n'avez pas fini de l'utiliser ! Le site return 0; à la fin de main causes fn pour sortir du champ d'application. Donc le temps que vous appeliez tick il n'y a aucune garantie que l'objet existe encore.

(La logique dans ~Runnable est totalement cassé. L'introduction du destructeur arrive bien trop tard -- l'objet est déjà au moins partiellement détruit).

6voto

L'approche consistant à utiliser l'héritage, le parent faisant office de contrôle pour le fil et les enfants mise en œuvre de les fonctions est une mauvaise idée en général. Les problèmes courants de cette approche proviennent de la construction et de la destruction :

  • si le thread est lancé à partir du constructeur du parent (contrôle), il peut commencer à fonctionner avant que le constructeur ne soit terminé et le thread peut appeler la fonction virtuelle avant que l'objet complet n'ait été entièrement construit.

  • si le fil est arrêté dans le destructeur du parent, alors au moment où le contrôle rejoint le fil, celui-ci exécute une méthode sur un objet qui n'existe plus.

Dans votre cas particulier, vous touchez le deuxième cas. Le programme commence à s'exécuter, et dans main le deuxième fil est lancé. À ce moment-là, il y a une course entre le fil principal et le nouveau fil lancé, si le nouveau fil est plus rapide (peu probable, car le démarrage du fil est une opération coûteuse), il appellera la méthode membre Tick qui sera envoyé à la commande finale. Fn::Tick .

Mais si le thread principal est plus rapide, il sortira de la portée de l'option main et il commencera la destruction de l'objet, il achèvera la destruction de l'objet. Fn et pendant la construction de l Runnable elle le fera join le fil. Si le fil principal est assez rapide, il arrivera jusqu'au join avant le second thread et attend là que le second thread appelle Tick sur le maintenant le surrégime final qui est Runnable::Tick . Notez que c'est Comportement indéfini et non garanti, puisque le second thread accède à un objet en cours de destruction.

En outre, il existe d'autres ordres possibles, comme par exemple, le deuxième thread pourrait envoyer des messages à Fn::Tick avant que le thread principal ne commence la destruction, mais il se peut qu'il ne termine pas la fonction avant que le thread principal ne détruise les Fn sous objet, dans ce cas, votre deuxième thread appellerait une fonction membre sur un objet mort.

Vous devriez plutôt suivre l'approche de la norme C++ : séparer les éléments contrôle de la logique pour construire complètement l'objet qui sera exécuté et le passer au thread pendant la construction. Notez que c'est le cas de la méthode Java Runnable ce qui est recommandé par rapport à l'extension de la Thread classe. Notez que, du point de vue de la conception, cette séparation est logique : la classe filetage gère l'exécution, et l'objet exécutable est le code à exécuter. Un thread n'est pas un téléscripteur mais plutôt ce qui contrôle l'exécution de la téléscripteur . Et dans votre code Runnable n'est pas quelque chose qui peut être exécuté, mais plutôt quelque chose qui exécute d'autres objets qui en dérivent.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X