std::atomic<bool> hat anderen Wert in anderem Thread



  • Guten Abend 🙂

    Ich versuche gerade ein Verhalten nachzuvollziehen und bräuchte dabei mal Hilfe.
    Eine Klasse von mir hat ein atomic Mitglied:

    std::atomic<bool> executionIsRunning{};
    

    Der Konstruktor setzt die Variable auf true:

    ls_std::ThreadPool::ThreadPool(const ls_std::ThreadPoolParameter &_parameter)
    {
      if(_parameter.amountOfWorkers < 2)
      {
        throw ls_std::IllegalArgumentException{};
      }
    
      this->executionIsRunning = true; // HERE!
      this->parameter = _parameter;
    }
    

    Nachdem ich diesen Konstruktor in meinem Code ausführe, rufe ich eine init Methode auf:

    void ls_std::ThreadPool::init()
    {
      this->executionWorker = std::async(std::launch::async, &ls_std::ThreadPool::_handleWorkers, this);
    }
    

    In der besagten _handleWorkers Methode ist executionIsRunning dann aber false:

    void ls_std::ThreadPool::_handleWorkers()
    {
      while(this->_isExecutionRunning()) // HERE executionIsRunning is FALSE!
      {
        // some code!
      }
    }
    

    _isExecutionRunning:

    bool ls_std::ThreadPool::_isExecutionRunning()
    {
      return this->executionIsRunning.load();
    }
    

    Meine Frage: Warum?
    Der Wert von isExecutionRunning wurde im Konstruktor auf true gesetzt und ist auch beim debuggen in der init Methode noch true. Nur, wenn es dann in den neuen Thread geht mittels async ist die Variable plötzlich false. Andere members, wie etwa parameter, welches ich ja auch im Konstruktor setze, hat auch im neuen Thread den richtigen Wert. Am Multithreading selbst kann es also nicht liegen. Irgendetwas geht bei der Verwendung von std::atomic schief bei mir...

    Hier die sequenzielle Ausführung in einem Unit Test (der eigentliche Aufruf):

    TEST_F(ThreadPoolTest, schedule)
    {    
      // create thread pool
    
      ls_std::ThreadPool threadPool{createThreadPoolParameter(3, 100)};
      threadPool.init();
    
      // ... more code
    }
    

    Hat irgendjemand eine Idee? Über jede Hilfe wäre ich dankbar 🙂
    Schönen Abend noch!

    Gruß,
    Patrick



  • Auf die Schnelle kann ich das Problem nicht sehen.

    Ich habe zwar das Wochenende keine Zeit mir das Problem am PC genauer anzugucken, aber insgesamt ist es bei solchen Problemen ratsam, dass für Demo Zwecke in ein minimales kompilierbares Beispiel zu packen, dass man einfach einem Compiler zum Fraß vorwerfen kann und das Problem reproduziert bekommt.



  • Hi Patrick,
    hast du geschaut, ob das Objekt, was du initialisierst, das gleiche ist, wie das was der Handler-Thread aufruft? Z.B. via gdb oder this-Addresse auf die Commandline schreiben? Passiert bei dem Thread-Interface immer super schnell, dass man versehentlich ein Objekt doch nochmal kopiert.



  • Guten Abend,

    Entschuldigt bitte die späte Antwort.
    @mhartung Tatsächlich ist es die gleiche Referenz, auch im neuen thread.
    Ich habe den Fehler vor ein paar Stunden aber endlich gefunden:

    • T1 initialisiert die Variable executionIsRunning und setzt sie auf true
    • T1 started T2 via async
    • T1 setzt die Variable im Desktruktor auf false
      TEST_F(ThreadPoolTest, schedule)
      {
        // create thread pool
    
        ls_std::ThreadPool threadPool{createThreadPoolParameter(3, 100)};
        threadPool.init();
    
        // ... some test data generation
    
        // create tasks
    
        auto task1 = std::bind(&ls_std_test::User::increaseSalary, user1, std::placeholders::_1);
        auto task2 = std::bind(&ls_std_test::User::increaseSalary, user1, std::placeholders::_1);
    
        // schedule 2 tasks using async
    
        threadPool.schedule(task1, std::reinterpret_pointer_cast<ls_std::IFunctionParameter>(userParameter1));
        threadPool.schedule(task2, std::reinterpret_pointer_cast<ls_std::IFunctionParameter>(userParameter2));
    
        // wait for finalizing thread pool tasks
    
        threadPool.finalize(); // FORUM INFO: this went wrong!
    
        // check result
    
        ASSERT_EQ(3300, user1.getSalary());
        ASSERT_EQ(2200, user2.getSalary());
    
        // destructor called and setting "executionIsRunning" to false
      }
    

    Eigentlich hätte der Destruktor das noch nicht tun dürfen, da ich eine finalize Methode dazwischen gepackt habe, die auf die Workers im Unit Test hätte warten sollen - diese finalize Methode scheint aber aktuell verbugt zu sein - als ich die Anweisung im Destruktor nämlich auskommentiert habe, hat es dann funktioniert und ich bin beim debuggen ein ganzes Stück weiter gekommen!

    @Schlangenmensch guter Punkt bzgl. Code zum Reproduzieren teilen - ganz klares Versäumnis auf meiner Seite. Das Projekt ist open source: LS - Standard Library und der besagte Test befindet sich hier.

    Danke für die Mühe!

    Gruß,
    Patrick



  • Dieser Beitrag wurde gelöscht!


  • @PadMad Hi, es ist zwar interessant zu sehen, aus welchem Projekt der Code komnmt. Aber: Ich möchte ,um zu Helfen, den relevanten Code ausführbar möglichst schnell zu sammen haben, also Copy & Paste von hier, ohne das ganze Projekt runter zu laden zu müssen.
    Ein minimales compilierbares Beispielt hat die Vorteile, dass genau das gegeben ist, außerdem der Code zum nachvollziehen des Problems auf jeden Fall da ist (wenn ich das richtig gesehen habe, hattest du die relevante Stelle im Eingangspost ja gar nicht erwähnt) und wahrscheinlich wärst du beim Erstellen des minimalen Beispiels schon selber über den eigentlichen Fehler gestolpert 😉