【问题标题】:如何有效地获取本地时间?
【发布时间】:2022-01-23 12:31:29
【问题描述】:

我正在开发一项服务。目前我需要为每个请求获取本地时间,因为它涉及系统调用,成本太高。

在我的情况下,像 200 毫秒这样的偏差对我来说是可以的。

那么维护存储local_hour 的变量并每200 毫秒更新一次的最佳方法是什么?

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;
}

【问题讨论】:

  • 您如何确定它“涉及系统调用”?在 linux 上 gettime 是在用户空间中实现的。
  • 程序启动时启动一个计时器线程,根据需要随时检查小时,并将结果存储在std::atomic_uint。其他线程可以从那里读取缓存的值。
  • 我不确定这是否是优化的正确位置。在我的旧电脑上,每秒可以运行大约 15M (= 15*10^6) std::chrono::system_clock::now()
  • 你能假设计算机是不动的吗?或者它可能从一个时区旅行到另一个时区?该服务是否需要一次保持几个月或更长时间?在这种情况下,您需要考虑政治家在程序启动后更改时区规则,并在无需重启即可更新时区数据库的平台上运行。

标签: c++ optimization time system-calls


【解决方案1】:

如果您希望您的主线程花费尽可能少的时间来获取当前时间,您可以启动一个后台线程来完成所有繁重的工作。

对于所有时间使用 std::chrono 类型。 这是一个例子,它使用了很多(非常有用的)来自 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;
}

【讨论】:

    【解决方案2】:

    您不需要为每个请求查询本地时间,因为小时不会每 200 毫秒更改一次。只需每小时更新一次本地小时变量

    最正确的解决方案是注册一个计时器事件,例如 Windows 上的计划任务或 Linux 上在每小时开始运行的 cronjobs。或者创建一个每小时运行的计时器并更新变量

    计时器的创建取决于平台,例如在 Windows 上使用 SetTimer,在 Linux 上使用 timer_create。这是一个使用boost::asio 的非常简单的解决方案,它假设您在准确的时间运行。您需要进行一些修改以使其可以随时运行,例如通过创建一次性计时器或休眠到下一小时

    #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
    }
    

    现在只需直接使用local_hour 而不是GetLocalHour()

    【讨论】:

    • 如果用户例如在 16:40 启动程序会怎样?这个程序不会在 17:40 之前返回“16”吗?
    • 如果用户在操作系统设置中更改本地时间怎么办?
    • @vll 是的,您需要更改第一个周期的持续时间
    猜你喜欢
    • 1970-01-01
    • 2016-06-19
    • 2014-07-08
    • 1970-01-01
    • 2018-07-02
    • 1970-01-01
    • 2013-07-12
    • 2012-04-10
    • 1970-01-01
    相关资源
    最近更新 更多