Search code examples
c++c++11timer

Can you implement a timer without a "sleep" in it using standard c++/c++11 only?


IMPORTANT UPDATE

Note: since this question is specifically about timers, its important to note there is a bug in gcc that if you are using std::condition_variable::wait_for (or wait_util) it uses the system clock even if you pass it a std::chrono::steady_clock time point. This means the timer is not monotonic - i.e. if you change the system time forward by a day then your timer may not trigger for a day + your timeout - if you change the time backwards your timer may trigger immediately.

See: condition_variable workaround for wait_until with system time change

The fix for this bug went into gcc v10+

END

I have the following code (hand-copied in):

// Simple stop watch class basically takes "now" as the start time and 
// returns the diff when asked for.
class stop_watch {...}

// global var
std::thread timer_thread;

void start_timer(int timeout_ms)
{
    timer_thread = std::thread([timeout_ms, this](){
        stop_watch sw;
        while (sw.get_elapsed_time() < timeout_ms)
        {
            // Here is the sleep to stop from hammering a CPU
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }

        // Do timeout things here...
        std::cout << "timed out!" << std::endl;
    })
}

I did not want to get too bogged down in the detail of the class I writing so this is a very cut-down version. The full class calls a function call-back and has a variable to cancel the timer etc...

I just wanted to focus on the "sleep" part. Can I implement something like this without a sleep or is there a better way to do it? - or is sleep perfectly good? - I was of the opinion that sleeps are generally a sign of bad design (I have read that a few places)... but I can't think of a way to implement a timer without one :(

Additional Note: The timer should have the requirement to be able to be stopped/woken at any time. Just adding that for clarity because it appears to affect what kind of solution to go for. In my original code (not this snippet) I used an atomic bool flag that can break out of the loop.


Solution

  • C++11 provides us with std::condition_variable. In your timer you can wait until your condition has been met:

    // Somewhere else, e.g. in a header:
    std::mutex mutex;
    bool condition_to_be_met{false};
    std::condition_variable cv;
    
    // In your timer:
    // ...
    std::unique_lock<std::mutex> lock{mutex};
    if(!cv.wait_for(lock, std::chrono::milliseconds{timeout_ms}, [this]{return condition_to_be_met;}))
    std::cout << "timed out!" << std::endl;
    

    You can find more information here: https://en.cppreference.com/w/cpp/thread/condition_variable

    To signal that the condition has been met do this in another thread:

    {
        std::lock_guard<std::mutex> lock{mutex}; // Same instance as above!
        condition_to_be_met = true;
    }
    cv.notify_one();