Search code examples
c++linuxc++14stdthread

std::thread doesn't execute C++ function


I have this simple function:

class Timer {
    std::atomic<bool> active{true};
    
    public:
         
        void setInterval(auto function, int interval);
        void stop();

};


void Timer::setInterval(auto function, int interval) {
    active = true;
    std::thread t([=]() {
        while(active.load()) {
            std::this_thread::sleep_for(std::chrono::milliseconds(interval));
            if(!active.load()) return;
            function();
        }
    });
    t.detach();
}

void Timer::stop() {
    active = false;
}

Now when I try to run it:

int main()
{
    printf("start\n");
    Timer* timer = new Timer();
    timer->setInterval([&]() {
        std::cout << "hello" << std::endl;
    },1000); 
    return 0;
};

Only the start printed from the main class, it never reaches the function inside the setInterval function. It just stops the app with no error.

The compile/link command:

/usr/bin/g++ -fdiagnostics-color=always -std=c++14 -pthread -g /home/vagrant/cpp/batch/main.cpp -o /home/vagrant/cpp/batch/main

Solution

  • Here is one of the many ways of correctlty using a thread object to run your timer.

    #include <atomic>
    #include <thread>
    #include <chrono>
    #include <iostream>
    
    class Timer {
      private:
        std::atomic<bool> active = false;
        std::unique_ptr<std::thread> timer_thread;
    
      public:
        // this class needs a destructor.
        ~Timer()
        {
            stop();
        }
    
        // this function name is misleading, 
        // void setInterval(auto function, int interval);
        //
        // let's rename it to what it does, also try to keep 
        // the function argument last, this makes the code
        // more legible when calling with a lambda.
        //
        // you do not know if 'cb', the caller's callable object
        // is copyable or moveable, so you should use perfect 
        // forwarding, that's only (readily) available from a 
        // template.
    
        template <typename Fn>
        void start(int interval, Fn&& cb)
        {
             stop();  // make sure we're stopped.
             active = true;
    
             // start the thread, the forward<> of the callback object is 
             // important!
             timer_thread = std::make_unique<std::thread>(
                 [this, ms = std::chrono::milliseconds(interval), cb = std::forward<Fn>(cb)]() {
                    while (active) // calls atomic<>::load() automatically
                    {
                        std::this_thread::sleep_for(ms);
                        if (active)
                            cb();
                    }
                });
        }
    
        // this is where we kill the thread, and wait for it to exit.
        void stop()
        {
            active = false;
            if (timer_thread)
            {
                timer_thread->join();
                timer_thread.reset();
            }
        }
    
        bool isRunning() const 
        {
            return active;
        }
    };
    
    int main()
    {
        std::cout << "start\n";
    
        Timer timer;  // There's absolutely no need to place timer on the heap.
        int n = 4;    // simple countdown to 1 for example.
    
        timer.start(1000, [&]() { 
             std::cout << "countdown: " << n << '\n';
             if (--n <= 0) 
                 timer.stop(); 
         });
    
        // We should wait for the timer to stop, before exiting the program.
        while (timer.isRunning()) {}
    
        return 0;
    };