基于c++11线程安全下在高并发下获取系统时间
很多高并发服务需要依赖于时间戳,但是大量的频繁的获取时间对于计算机来说是一个不可忽视的消耗.在开发实战的过程中确实发现了这个问题.
我们不妨考虑使用多线程去解决这个问题,思路如下 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属性来支持它