很多高并发服务需要依赖于时间戳,但是大量的频繁的获取时间对于计算机来说是一个不可忽视的消耗.在开发实战的过程中确实发现了这个问题.

我们不妨考虑使用多线程去解决这个问题,思路如下 1.开启单独线程更新时间,这样获取时间的频率就不是很高了,由原来几万次每秒缩减到1000次每秒,这样以减少系统获取时间的负担,但是锁的开销是不可避免的. 2.考虑使用c11static静态成员实现单例以保证 线程安全(其实很简单,在单例模式中,将类构建限制static写入getinstance方法中,即可做到线程安全),说的不够清楚还是看代码自己体会吧.



#ifndef __SYSTIMECLOCK_
#define __SYSTIMECLOCK_

#include <string>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include "config.h"
#include <thread>
#include <memory>
#include <shared_mutex>
#include "logger.h"
#include "spdlog/spdlog.h"

class SystemClock
{
public:
    SystemClock(const SystemClock &) = delete;
    SystemClock &operator=(const SystemClock &) = delete;

    virtual ~SystemClock() {
        if (handler->joinable())
        {
            std::unique_lock<std::shared_mutex> lock(shard_mtx);
            handler->join();
        }
    };

    static SystemClock *GetInstance()
    {
        static SystemClock s_cfg;
        return &s_cfg;
    }


    int time_now() {
        std::shared_lock<std::shared_mutex> lock(shard_mtx);
        return  m_time_now;
    }

    int run()
    {
        status = true;
        handler = new std::thread(worker_thread, this);
        return 0;
    }
    
private:
    static void worker_thread(void *arg)
    {
        SystemClock *p = (SystemClock *)arg;
        while (p->status == true)
        {
            std::this_thread::sleep_for(std::chrono::microseconds(1));
            std::unique_lock<std::shared_mutex> lock(p->shard_mtx);
            p->m_time_now = p->GetCurTimeInt();
            lock.unlock();
        }
    }


    long GetCurTimeInt()
    {
        std::string strTime = boost::posix_time::to_iso_string(boost::posix_time::microsec_clock::local_time());
        int pos1 = strTime.find('T');
        int pos2 = strTime.find('.');
        std::string result = strTime.substr(pos1 + 1, 6) + strTime.substr(pos2 + 1, 3);
        return atoi(result.c_str());
    }

    SystemClock() {
       
    }
public:
    std::shared_ptr<spdlog::logger> m_pLogger;


protected:
    long  m_time_now;
    std::shared_mutex shard_mtx;
    std::mutex mtx;
    std::thread *handler;
    bool status = false;



private:



#define  TIMECOST  1000000
    int test_bench()
    {
        auto g_log = CLogger::GetInstance()->GetLogger("test get time");

        g_log->error("{}次耗时对比", TIMECOST);
        SystemClock::GetInstance()->run();
        for (int i = 0; i < TIMECOST; i++)
        {
            int xx = 0;
            xx = GetCurTimeInt();
        }
        g_log->error("{}次耗时对比", TIMECOST);

        g_log->error("{}次耗时对比", TIMECOST);
        SystemClock::GetInstance()->run();
        for (int i = 0; i < TIMECOST; i++)
        {
            int xx = SystemClock::GetInstance()->time_now();
            g_log->error("{}次耗时对比", xx);

        }
        g_log->error("{}次耗时对比", TIMECOST);

    }
};



#endif


优化后的效果

/*
采用 std::mutex

// 优化前:6.9秒
[2020-08-27 10:08:27.768] [test get time] [error] 1000000次耗时对比
[2020-08-27 10:08:34.179] [test get time] [error] 1000000次耗时对比
// 优化后:0.05秒
[2020-08-27 10:08:34.183] [test get time] [error] 1000000次耗时对比
[2020-08-27 10:08:34.233] [test get time] [error] 1000000次耗时对比
*/


/*
采用std::shared_mutex
优化前:6.719s
[2020-08-27 10:17:01.860] [test get time] [error] 1000000次耗时对比
[2020-08-27 10:17:08.579] [test get time] [error] 1000000次耗时对比
优化后:0.034  相对std::mtx 优化50%
[2020-08-27 10:17:08.579] [test get time] [error] 1000000次耗时对比
[2020-08-27 10:17:08.612] [test get time] [error] 1000000次耗时对比
*/

结论

采用多线程获取系统时间虽然是有加锁解锁操作,但是相对操作系统调用系统时间戳来说代价要小的多,到底小多少呢,数据表示大概两个量级,这个实验更多的收获是体会到操作系统api消耗代价和加锁解锁操作代价性能的比较,让我有了对锁一个更具象的认知,在以后操作中能起到思路灵感的的风向标.

ps:

  • 代码中使用了部分spdlog,主要是偷懒免去写事件函数的,不喜欢的同学请交给摊主处理 XD;
  • std::shared_mutex在vs2015中是支持的, 但是如果你使用c++编译器请选择c++14属性来支持它