阻塞队列部分借用该老兄的。博主原创文章链接:https://blog.csdn.net/weixin_50437588/article/details/128434180
本文开发了一款跨平台的C++日志系统。
/*
重点实现一个线程安全的缓冲队列:
1、首先使用STL库中的deque作为基本容器;
2、然后定义一个固定大小的容量capacity,防止无限制扩容下去导致内存耗尽;
3、之后需要两个条件变量Productor和Consumer,模拟生产者和消费者;
4、条件变量必须得有互斥量辅助,所以还需一个互斥量mutex。
*/
//BlockQueue.h
#ifndef BLOCKQUEUE_H
#define BLOCKQUEUE_H
#include <mutex>
#include <deque>
#include <condition_variable>
#include <assert.h>
#include <chrono>
template<class T>
class BlockQueue {
public:
explicit BlockQueue(unsigned int maxCapacity = 1024);
~BlockQueue();
void close(); // 关闭队列
void flush(); // 刷新队列,通知取队列的线程
void clear(); // 清空队列
T front(); // 模拟队列的front函数 ,返回队尾元素
T back(); // 模拟队列的back函数 ,返回队首元素
unsigned int get_size(); // 获取队列 当前有多少元素
unsigned int get_capacity(); // 获取队列容量
void push_back(const T& item); // 队尾插入数据
void push_front(const T& item); // 队首插入数据
bool empty(); // 判断队列为空
bool full(); // 判断队列为满
bool pop(T& item); // 删除队首元素并存入 item中
bool pop(T& item, int timeout); // 删除队首元素并存入 item中,最多等待timeout毫秒
private:
std::deque<T> deq;
unsigned int capacity;
std::mutex mtx;
bool isClose;
std::condition_variable Consumer;
std::condition_variable Producer;
};
template<class T>
BlockQueue<T>::BlockQueue(unsigned int maxCapacity) :capacity(maxCapacity){
assert(maxCapacity > 0);
isClose = false;
}
template<class T>
BlockQueue<T>::~BlockQueue(){
close();
};
template<class T>
void BlockQueue<T>::close(){
{
std::lock_guard<std::mutex> locker(mtx);
deq.clear();
isClose = true;
}
Producer.notify_all();
Consumer.notify_all();
};
template<class T>
void BlockQueue<T>::flush(){
Consumer.notify_one();
}
template<class T>
void BlockQueue<T>::clear(){
std::lock_guard<std::mutex> lock(mtx);
deq.clear();
}
template<class T>
T BlockQueue<T>::front(){
std::lock_guard<std::mutex> lock(mtx);
return deq.front();
}
template<class T>
T BlockQueue<T>::back(){
std::lock_guard<std::mutex> lock(mtx);
return deq.back();
}
template<class T>
unsigned int BlockQueue<T>::get_size(){
std::lock_guard<std::mutex> lock(mtx);
return deq.size();
}
template<class T>
unsigned int BlockQueue<T>::get_capacity(){
std::lock_guard<std::mutex> lock(mtx);
return capacity;
}
template<class T>
void BlockQueue<T>::push_back(const T& item){
//使用条件变量前应先对互斥量上锁,此时选择unique_lock而非lock_guard
//因为unique_lock在上锁后允许临时解锁再加锁,而lock_guard上锁后只能在离开作用域时解锁
std::unique_lock<std::mutex> lock(mtx);
while (deq.size() >= capacity) {
//若队列已满,则生产者线程进入阻塞状态,自动对互斥量解锁,被唤醒后又自动对互斥量加锁
Producer.wait(lock);
}
deq.push_back(item); //向队列中放入数据
Consumer.notify_one(); //唤醒一个阻塞的消费者线程
}
template<class T>
void BlockQueue<T>::push_front(const T& item){
std::unique_lock<std::mutex> lock(mtx);
while (deq.size() >= capacity){
Producer.wait(lock);
}
deq.push_front(item);
Consumer.notify_one();
}
template<class T>
bool BlockQueue<T>::empty(){
std::lock_guard<std::mutex> lock(mtx);
return deq.empty();
}
template<class T>
bool BlockQueue<T>::full(){
std::lock_guard<std::mutex> lock(mtx);
return deq.size() >= capacity;
}
template<class T>
bool BlockQueue<T>::pop(T& item){
std::unique_lock<std::mutex> lock(mtx);
while (deq.empty()){
//若队列空,则消费者线程进入阻塞,等待被唤醒
Consumer.wait(lock);
if (isClose)
return false;
}
item = deq.front(); //当作删除数据的拷贝,把头部数据放到item里面
deq.pop_front(); //删除队列头部数据
Producer.notify_one(); //唤醒一个阻塞的生产者线程
return true;
}
//设计为带计时器的pop函数。若队列在超时时间后仍一直为空,则立刻返回false。 单位为毫秒
template<class T>
bool BlockQueue<T>::pop(T& item, int timeout){
std::unique_lock<std::mutex> lock(mtx);
while (deq.empty()){
//阻塞时间超过指定的timeout,直接返回false
if (Consumer.wait_for(lock, std::chrono::microseconds(timeout)) == std::cv_status::timeout)
return false;
if (isClose)
return false;
}
item = deq.front();
deq.pop_front();
Producer.notify_one();
return true;
}
#endif // BLOCKQUEUE_H
//Log.h
#ifndef LOG_H
#define LOG_H
#include <stdio.h>
#include <iostream>
#include <string>
#include <stdarg.h>
#include <thread>
#include <fstream>
#include <sstream>
#include "BlockQueue.h"
using namespace std;
class Log{
public:
//C++11以后,使用局部变量懒汉不用加锁
static Log *getInstance(){
static Log instance;
return &instance;
}
static void *flush_log_thread(){
Log::getInstance()->async_write_log();
return nullptr;
}
//可选择的参数有日志文件、日志缓冲区大小、最大行数以及最长日志条队列
bool init(const string& file_name, int close_log, int log_buf_size = 8192, int split_lines = 5000000, int max_queue_size = 0);
void write_log(int level, const char *format, ...);
void flush(void);
int get_close_log(){
return m_close_log;
}
void set_close_log(int close_log){
m_close_log = close_log;
}
private:
Log(); //私有化构造函数
virtual ~Log();
void *async_write_log(){
string single_log;
//从阻塞队列中取出一个日志string,写入文件
while (m_log_queue->pop(single_log)){ //这个函数如果队列为空会一直阻塞在这。
m_mutex.lock();
//fputs(single_log.c_str(), m_fp); // 虽然 gets()、fgets()、puts()、fputs() 都是字符串处理函数,但它们都包含在 stdio.h
m_file << single_log << endl;
m_mutex.unlock();
}
return nullptr;
}
private:
string m_dir_name; //路径名
string m_log_name; //log文件名
int m_split_lines; //日志最大行数
char *m_buf;
int m_log_buf_size; //日志缓冲区大小
long long m_count; //日志行数记录
int m_today; //因为按天分类,记录当前时间是那一天
//FILE *m_fp; //打开log的文件指针 FILE 是C语言的stdio.h里面预定义的一个结构体,是管理文件流的一种结构。
ofstream m_file; //打开log的文件指针
BlockQueue<string> *m_log_queue; //阻塞队列
bool m_is_async; //是否同步标志位
std::mutex m_mutex;
int m_close_log; //关闭日志 1表示关闭,0表示打开
};
#define LOG_DEBUG(format, ...) if(0 == m_close_log) {Log::getInstance()->write_log(0, format, ##__VA_ARGS__); Log::getInstance()->flush();}
#define LOG_INFO(format, ...) if(0 == m_close_log) {Log::getInstance()->write_log(1, format, ##__VA_ARGS__); Log::getInstance()->flush();}
#define LOG_WARN(format, ...) if(0 == m_close_log) {Log::getInstance()->write_log(2, format, ##__VA_ARGS__); Log::getInstance()->flush();}
#define LOG_ERROR(format, ...) if(0 == m_close_log) {Log::getInstance()->write_log(3, format, ##__VA_ARGS__); Log::getInstance()->flush();}
#endif
// Log.cpp
#include <string>
#include <cstring>
#include <time.h>
#include <ctime>
#include <stdio.h>
#include <stdarg.h>
#include <chrono>
#include "Log.h"
using namespace std;
using namespace std::chrono;
start 在VS中定义一下这个,否者会报错
//#if _MSC_VER
//#define snprintf _snprintf
//#endif
end
Log::Log(){
m_count = 0;
m_is_async = false;
}
Log::~Log(){
if (m_file.is_open()){
m_file.flush();
m_file.clear();
m_file.close();
}
}
//异步需要设置阻塞队列的长度,同步不需要设置
bool Log::init(const string& file_name, int close_log, int log_buf_size, int split_lines, int max_queue_size){
//如果设置了max_queue_size,则设置为异步
if (max_queue_size >= 1){
m_is_async = true;
m_log_queue = new BlockQueue<string>(max_queue_size);
//pthread_t tid;
//flush_log_thread为回调函数, 这里表示创建线程异步写日志
//pthread_create(&tid, NULL, flush_log_thread, NULL);
std::thread t(flush_log_thread);
t.detach();
}
m_close_log = close_log;
m_log_buf_size = log_buf_size;
m_buf = new char[m_log_buf_size];
memset(m_buf, '\0', m_log_buf_size);
m_split_lines = split_lines;
stringstream stream1;
time_t t = time(NULL); //获取1970年1月1日0点0分0秒到现在经过的秒数
struct tm *sys_tm = localtime(&t); //将秒数转换为本地时间,年从1900算起,需要+1900,月为0-11,所以要+1
struct tm my_tm = *sys_tm; //复制一个对象
char temp[32];
sprintf(temp, "%d_%02d_%02d_", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday);
m_today = my_tm.tm_mday;
int index = file_name.rfind('/', file_name.size());
//cout << " index = " << index << endl;
if (index == -1){ //未找到
stream1 << temp << file_name;
}
else{ //找到了
string m_dir_name = file_name.substr(0, index + 1);
string m_log_name = file_name.substr(index + 1, file_name.size() - index - 1);
//cout << " dir_name = " << dir_name << endl;
//cout << " log_name = " << log_name << endl;
stream1 << m_dir_name << temp << m_log_name;
}
string log_full_name;
stream1 >> log_full_name;
//cout << log_full_name.c_str() << endl;
m_file.open(log_full_name, ios::app); //以追加模式打开文件
if (!m_file.is_open()){
return false;
}
return true;
}
void Log::write_log(int level, const char *format, ...){
system_clock::time_point time_point_now = system_clock::now(); // 获取当前时间点
system_clock::duration duration_since_epoch = time_point_now.time_since_epoch(); // 从1970-01-01 00:00:00到当前时间点的时长
time_t microseconds_since_epoch = duration_cast<microseconds>(duration_since_epoch).count(); // 将时长转换为微秒数
time_t seconds_since_epoch = microseconds_since_epoch / 1000000; // 将时长转换为秒数
std::tm current_time = *std::localtime(&seconds_since_epoch); // 获取当前时间(精确到秒)
time_t tm_microsec = microseconds_since_epoch % 1000; // 当前时间的微妙数
time_t tm_millisec = microseconds_since_epoch / 1000 % 1000; // 当前时间的毫秒数
char temp[36]{ 0 };
sprintf(temp, "%04d-%02d-%02d %02d:%02d:%02d.%03d%03d", current_time.tm_year + 1900, current_time.tm_mon + 1, current_time.tm_mday,
current_time.tm_hour, current_time.tm_min, current_time.tm_sec, static_cast<int>(tm_millisec), static_cast<int>(tm_microsec));
std::string ExactTime(temp);
//cout << ExactTime << endl;
string s;
switch (level)
{
case 0:
s = "[debug]:";
break;
case 1:
s = "[info]: ";
break;
case 2:
s = "[warn]: ";
break;
case 3:
s = "[erro]: ";
break;
default:
s = "[info]: ";
break;
}
m_mutex.lock();
m_count++;
if (m_today != current_time.tm_mday || m_count % m_split_lines == 0) //everyday log
{
string new_log;
m_file.clear();
m_file.close();
char tail[16] = { 0 };
sprintf(tail, "%d_%02d_%02d_", current_time.tm_year + 1900, current_time.tm_mon + 1, current_time.tm_mday);
if (m_today != current_time.tm_mday){
new_log = m_dir_name + std::string(tail) + m_log_name;
m_today = current_time.tm_mday;
m_count = 0;
}
else{
char temp[16] = { 0 };
sprintf(temp, ".%lld", m_count / m_split_lines);
new_log = m_dir_name + std::string(tail) + m_log_name + std::string(temp);
}
m_file.open(new_log, ios::app); //以追加模式打开文件
}
// ----------------------------------------------------------------------------------------------------
m_mutex.unlock();
va_list valst;
va_start(valst, format);
string log_str;
m_mutex.lock();
//写入的具体时间内容格式
log_str += ExactTime + " " + s;
//int n = sprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ",
// my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday,
// my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s);
int m = vsnprintf(m_buf, m_log_buf_size - 1, format, valst);
m_buf[m] = '\0';
//m_buf[m + 1] = '\0';
log_str += m_buf;
m_mutex.unlock();
if (m_is_async && !m_log_queue->full()){
m_log_queue->push_back(log_str);
}
else{
m_mutex.lock();
m_file << log_str << endl;
//fputs(log_str.c_str(), m_fp);
m_mutex.unlock();
}
va_end(valst);
}
void Log::flush(void)
{
m_mutex.lock();
//强制刷新写入流缓冲区
m_file.flush();
//m_file.clear();
m_mutex.unlock();
}
test.cpp
#include <iostream>
#include <stdio.h>
#include <string>
#include "Log.h"
#include "BlockQueue.h"
#include <fstream>
#include <sstream>
#include <time.h>
#include <stdio.h>
#include <stdarg.h>
#include <chrono>
#include <ctime>
using namespace std;
using namespace std::chrono;
void threadFun(int m_close_log){
for (int j = 20000; j <= 35000; j++){ //60000条log
LOG_DEBUG("%s 111111111 %d ============= ", "main", j);
LOG_INFO("%s 111111111 %d ============= ", "main", j);
LOG_WARN("%s 111111111 %d ============= ", "main", j);
LOG_ERROR("%s 111111111 %d ============= ", "main", j);
}
}
int main(){
cout << "hello world" << endl;
int m_close_log = 0;
int cnt = 0;
Log::getInstance()->init("./ServerLog.log", m_close_log, 2000, 800000, 1000);
thread yy(threadFun, 0);
// yy.detach();
for (int j = 0; j <= 15000; j++){ //60000条log
LOG_DEBUG("%s 111111111 %d ============= ", "main", j);
LOG_INFO("%s 111111111 %d ============= ", "main", j);
LOG_WARN("%s 111111111 %d ============= ", "main", j);
LOG_ERROR("%s 111111111 %d ============= ", "main", j);
}
yy.join();
//while (1){};
system("pause");
return 0;
}
g++ test.cpp Log.h Log.cpp BlockQueue.h -o app -std=c17 -lpthread
在VS中也可以直接编译