Home > Mobile >  How to get local hour efficiently?
How to get local hour efficiently?

Time:12-22

I'm developing a service. Currently I need to get local hour for every request, since it involves system call, it costs too much.

In my case, some deviation like 200ms is OK for me.

So what's the best way to maintain a variable storing local_hour, and update it every 200ms?

static int32_t GetLocalHour() {
    time_t t = std::time(nullptr);
    if (t == -1) { return -1;  }
    struct tm *time_info_ptr = localtime(&t);
    return (nullptr != time_info_ptr) ? time_info_ptr->tm_hour : -1;
}

CodePudding user response:

If you want your main thread to spend as little time as possible on getting the current hour you can start a background thread to do all the heavy lifting.

For all things time use std::chrono types. Here is the example, which uses quite a few (very useful) multithreading building blocks from C .

#include <chrono>
#include <future>
#include <condition_variable>
#include <mutex>
#include <atomic>
#include <iostream>

// building blocks
// std::future/std::async, to start a loop/function on a seperate thread
// std::atomic, to be able to read/write threadsafely from a variable
// std::chrono, for all things time
// std::condition_variable, for communicating between threads. Basicall a signal that only signals that something has changed that might be interesting
// lambda functions : anonymous functions that are useful in this case for starting the asynchronous calls and to setup predicates (functions returning a bool)
// std::mutex : threadsafe access to a bit of code
// std::unique_lock : to automatically unlock a mutex when code goes out of scope (also needed for condition_variable)


// helper to convert time to start of day
using days_t = std::chrono::duration<int, std::ratio_multiply<std::chrono::hours::period, std::ratio<24> >::type>;

// class that has an asynchronously running loop that updates two variables (threadsafe)
// m_hours and m_seconds (m_seconds so output is a bit more interesting)
class time_keeper_t
{
public:

    time_keeper_t() :
        m_delay{ std::chrono::milliseconds(200) }, // update loop period
        m_future{ std::async(std::launch::async,[this] {update_time_loop(); }) } // start update loop
    {
        // wait until asynchronous loop has started
        std::unique_lock<std::mutex> lock{ m_mtx };

        // wait until the asynchronous loop has started.
        // this can take a bit of time since OS needs to schedule a thread for that
        m_cv.wait(lock, [this] {return m_started; });
    }

    ~time_keeper_t()
    {
        // threadsafe stopping of the mainloop
        // to avoid problems that the thread is still running but the object 
        // with members is deleted.
        {
            std::unique_lock<std::mutex> lock{ m_mtx };
            m_stop = true;
            m_cv.notify_all(); // this will wakeup the loop and stop
        }

        // future.get will wait until the loop also has finished
        // this ensures no member variables will be accessed
        // by the loop thread and it is safe to fully destroy this instance
        m_future.get();
    }

    // inline to avoid extra calls
    inline int hours() const
    {
        return m_hours;
    }

    // inline to avoid extra calls
    inline int seconds() const
    {
        return m_seconds;
    }

private:
    void update_time()
    {
        m_now = std::chrono::steady_clock::now();
        
        std::chrono::steady_clock::duration tp = m_now.time_since_epoch();

        // calculate back till start of day
        days_t days = duration_cast<days_t>(tp);
        tp -= days;

        // calculate hours since start of day
        auto hours = std::chrono::duration_cast<std::chrono::hours>(tp);
        tp -= hours;
        m_hours = hours.count();

        // seconds since start of last hour
        auto seconds = std::chrono::duration_cast<std::chrono::seconds>(tp);
        m_seconds = seconds.count() % 60;
    }

    void update_time_loop()
    {
        std::unique_lock<std::mutex> lock{ m_mtx };

        update_time();

        // loop has started and has initialized all things time with values
        m_started = true;
        m_cv.notify_all();
        
        // stop condition for the main loop, put in a predicate lambda
        auto stop_condition = [this]() 
        {
            return m_stop; 
        };

        while (!m_stop)
        {
            // wait until m_cv is signaled or m_delay timed out
            // a condition variable allows instant response and thus
            // is better then just having a sleep here.
            // (imagine a delay of seconds, that would also mean stopping could 
            // take seconds, this is faster)
            m_cv.wait_for(lock, m_delay, stop_condition);
            if (!m_stop) update_time();
        }
    }

    std::atomic<int> m_hours;
    std::atomic<int> m_seconds;
    std::mutex m_mtx;
    std::condition_variable m_cv;
    bool m_started{ false };
    bool m_stop{ false };

    std::chrono::steady_clock::time_point m_now;
    std::chrono::steady_clock::duration m_delay;

    std::future<void> m_future;
};


int main()
{
    time_keeper_t time_keeper;

    // the mainloop now just can ask the time_keeper for seconds
    // or in your case hours. The only time needed is the time
    // to return an int (atomic) instead of having to make a full
    // api call to get the time.
    for (std::size_t n = 0; n < 30;   n)
    {
        std::cout << "seconds now = " << time_keeper.seconds() << "\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    return 0;
}

CodePudding user response:

You don't need to query local time for every request because hour doesn't change every 200ms. Just update the local hour variable every hour

The most correct solution would be registering to a timer event like scheduled task on Windows or cronjobs on Linux that runs at the start of every hour. Alternatively create a timer that runs every hour and update the variable

The timer creation depends on the platform, for example on Windows use SetTimer, on Linux use timer_create. Here's a very simple solution using boost::asio which assumes that you run on the exact hour. You'll need to make some modification to allow it to run at any time, for example by creating a one-shot timer or by sleeping until the next hour

#include <chrono>
using namespace std::chrono_literals;

int32_t get_local_hour()
{
    time_t t = std::time(nullptr);
    if (t == -1) { return -1;  }
    struct tm *time_info_ptr = localtime(&t);
    return (nullptr != time_info_ptr) ? time_info_ptr->tm_hour : -1;
}

static int32_t local_hour = get_local_hour();
bool running = true;

// Timer callback body, called every hour
void update_local_hour(const boost::system::error_code& /*e*/,
    boost::asio::deadline_timer* t)
{
    while (running)
    {
        t->expires_at(t->expires_at()   boost::posix_time::hour(1));
        t->async_wait(boost::bind(print,
            boost::asio::placeholders::error, t, count));
        local_hour = get_local_hour();
    }
}

int main()
{
    boost::asio::io_service io;

    // Timer that runs every hour and update the local_hour variable
    boost::asio::deadline_timer t(io, boost::posix_time::hour(1));
    t.async_wait(boost::bind(update_local_hour,
        boost::asio::placeholders::error, &t));

    running = true;
    io.run();
    std::this_thread::sleep_for(3h);
    running = false; // stop the timer
}

Now just use local_hour directly instead of GetLocalHour()

  • Related