Zyklisches Ausführen einer Funktion und Threadsicheres stoppen.



  • Hallo zusammen

    Auf der Suche nach einer Funktion die mir eine andere Funktion zyklisch aufruft bin ich auf den folgenden Artikel gestoßen.
    https://stackoverflow.com/questions/30425772/c-11-calling-a-c-function-periodically

    class CallBackTimer
    {
    public:
        CallBackTimer()
        :_execute(false)
        {}
    
        ~CallBackTimer() {
            if( _execute.load(std::memory_order_acquire) ) {
                stop();
            };
        }
    
        void stop()
        {
            _execute.store(false, std::memory_order_release);
            if( _thd.joinable() )
                _thd.join();
        }
    
        void start(int interval, std::function<void(void)> func)
        {
            if( _execute.load(std::memory_order_acquire) ) {
                stop();
            };
            _execute.store(true, std::memory_order_release);
            _thd = std::thread([this, interval, func]()
            {
                while (_execute.load(std::memory_order_acquire)) {
                    func();                   
                    std::this_thread::sleep_for(
                    std::chrono::milliseconds(interval));
                }
            });
        }
    
        bool is_running() const noexcept {
            return ( _execute.load(std::memory_order_acquire) && 
                     _thd.joinable() );
        }
    
    private:
        std::atomic<bool> _execute;
        std::thread _thd;
    };
    

    Die zyklische Ausführung in der while Schleife ist klar.
    Was ich allerdings nicht ganz verstehe ist der stop mechanismus mit dem std::atomic

    Nachdem ich die funktion zyklisch gestartet habe rufe ich irgendwann stop auf und die schleife wird auch beendet.
    In manchen Fällen verweilt mein ausführung aber in _thd.join(); und die while schleife wird nicht beendet.

    Da ich das mit dem atomic nicht ganz verstehe, tue ich mir schwer zu sagen ob der Code in dem Post richtig ist.
    Kann mir da jemand etwas unterstützung geben und eventuell sagen was zu dem besagten verhalten führen kann.



  • Was ist "zyklisch aufrufen"?



  • zyklisch: wiederkehrend, in gleichen Abständen
    aufrufen: eine Funktion ausführen.

    ?



  • @booster

    @booster sagte in Zyklisches Ausführen einer Funktion und Threadsicheres stoppen.:

    In manchen Fällen verweilt mein ausführung aber in _thd.join(); und die while schleife wird nicht beendet.

    Einen Fehler diesbzüglich finde ich keinen. Wenn du jedoch beim starten ein _thd.detach() hinzufügst, so wie es in dem Stackoverflow Beitrag steht, bekommst du ein Synchronisationsproblem par excellence.

    Kann mir da jemand etwas unterstützung geben und eventuell sagen was zu dem besagten verhalten führen kann.

    Nebenläufigkeit. Ich glaube so etwas muss man mal selbst gedebuggt haben:

    #include <iostream>
    #include <string>
    #include <fstream>
    #include <thread>
    #include <algorithm>
    #include <type_traits>
    #include <functional>
    #include <atomic>
    
    class BuggyCallBackTimer
    {
    public:
        BuggyCallBackTimer()
            :_execute(false)
        {}
    
        ~BuggyCallBackTimer() {
            if(_execute) {
                stop();
            };
        }
    
        void stop()
        {
            _execute = false;
    //  std::this_thread::sleep_for(std::chrono::milliseconds(300)); // dämliches Workaround
            if ( _thd.joinable() )
                _thd.join();
        }
    
        void start(int interval, std::function<void(void)> func)
        {
            if (_execute) {
                stop();
            };
            _execute = true;
            _thd = std::thread([this, interval, func]()
            {
                while (_execute) {
                    func();
                    std::this_thread::sleep_for(std::chrono::milliseconds(interval));
                }
            });
            _thd.detach(); //  Seiteneffekt _thd.join() funktioniert nicht mehr
        }
    
        bool is_running() const noexcept {
            return (_execute && _thd.joinable() );
        }
    
    private:
        std::atomic<bool> _execute;
        std::thread _thd;
    };
    
    
    void FunctionA(void)
    {
        static int i = 0;
        printf("A %i\n", i++);
    }
    
    
    void FunctionB(void)
    {
        static int j = 0;
        printf("B %i\n", j++);
    }
    
    
    int main()
    {
        BuggyCallBackTimer T;
    
        T.start(100, FunctionA);
    
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    
        T.stop();                   // Setzt T._execute auf false.
    
        T.start(100, FunctionB);    // Setzt T._execute wieder auf true. Kommt in der Zwischenzeit der erste Thread nicht zum Zuge und beendet sich, läuft dieser dadurch immer weiter.
    
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        return 0;
    }
    


  • Hallo Quiche

    Du setzt in deinem Beispiel _execute je nach dem einfach auf true oder false.
    bzw fragst einfach den status mit dem == Operator ab.

    Wo ist den da der Unterschied zu

    _execute.store(true, std::memory_order_release);
    _execute.store(false, std::memory_order_release);
    und
    _execute.load(std::memory_order_acquire)



  • @booster
    Das weiß ich leider nicht genau, würde aber vermuten das es sich um eine Optimierung des Zugriffsverhalten handelt (siehe Links). Ich habe es einfach mal umgeschrieben damit es verständlicher wird.

    Aber std::atomic ist eine von vielen Möglichkeiten zur Synchronisation in Multi-Threaded Anwendungen. In Multi-Threaded Anwendungen können aufgrund dessen das jeder Thread zu jeder Zeit an jeder Stelle unbrochen werden kann, inkonsistente Zustände auftauchen. Und Synchronisation soll dies verhindern.

    In unserem Fall soll unter anderem das bool _execute die Synchronisation machen. Da aber potenziell X Threads auf diese Variable zugreifen können und potenziell auch beim schreiben auf _exceute unterbrochen werden können, wurde der Zugriff darauf atomar gemacht. Ganz einfach ausgedrückt: Der Thread darf beim Schreiben auf _execute nicht unterbrochen werden.

    Ich hoffe ich verwirre dich damit nicht zu sehr. 🙂

    https://en.wikipedia.org/wiki/Synchronization_(computer_science)
    https://en.wikipedia.org/wiki/Dining_philosophers_problem
    https://en.cppreference.com/w/cpp/atomic/atomic
    https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering


Log in to reply