Linux——日志的编写与线程池

server/2024/12/22 14:05:48/

目录

前言

一、日志的编写

二、线程池

1.线程池基本原理

2.线程池作用 

3.线程池的实现 


前言

学了很多线程相关的知识点,线程控制、线程互斥、线程同步,今天我们将他们做一个总结,运用所学知识写一个较为完整的线程池,同时把日志编写也学一下。

一、日志的编写

在企业开发过程中,经常会通过打印日志来查看当前项目的运行情况。写一个日志难度并不大,用到的都是之前学的知识,注释写得比较详细,代码如下。

Log.hpp

#pragma once#include<iostream>
#include<cstdarg>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
using namespace std;
enum{Debug = 0,Info,Warnig,Error,Fatal
};enum{Screen = 10,OneFile,ClassFile
};string LevelToString(int level)
{switch (level){case Debug:return "Debug";case Info:return "Info";case Warnig:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "Unkonw"; }
}const int default_style = Screen;
const string default_filename = "Log.";
const string logdir = "log";class Log  
{
public:Log(int style = default_style,string filename = default_filename):_style(style),_filename(filename){mkdir(logdir.c_str(),0775);}//更改打印方式void Enable(int style){_style = style;}//时间戳转化为年月日时分秒string GetTime(){time_t currtime = time(nullptr);struct tm* curr = localtime(&currtime);char time_buffer[128];snprintf(time_buffer,sizeof(time_buffer),"%d-%d-%d %d:%d:%d",curr->tm_year+1900,curr->tm_mon+1,curr->tm_mday,curr->tm_hour,curr->tm_min,curr->tm_sec);return time_buffer;}//写入到文件中void WriteLogToOneFile(const string& logname,const string& message){FILE* fp = fopen(logname.c_str(),"a");if(fp==nullptr){perror("fopen filed");exit(-1);}fprintf(fp, "%s\n", message.c_str());fclose(fp);}//打印日志void WriteLogToClassFile(const string& levelstr,const string& message){string logname = logdir;logname+="/";logname+=_filename;logname+=levelstr;WriteLogToOneFile(logname,message);}void WriteLog(const string& levelstr,const string& message){switch (_style) {case Screen:cout<<message<<endl;//打印到屏幕中break;case OneFile:WriteLogToClassFile("all",message);//给定all,直接写到all里break;case ClassFile:WriteLogToClassFile(levelstr,message);//写入levelstr里break;default:break;}}//打印日志void LogMessage(int level,const char* format,...){char rightbuffer[1024];//处理消息va_list args;   //va_list 是指针va_start(args,format);//初始化va_list对象,format是最后一个确定的参数//现在args指向了可变参数部分vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中va_end(args);char leftbuffer[1024];//处理日志等级、pid、时间string levelstr = LevelToString(level);string currtime = GetTime();string idstr = to_string(getpid());snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str(),currtime.c_str(),idstr.c_str());string loginfo = leftbuffer;loginfo+=rightbuffer;WriteLog(levelstr,loginfo);}//提供接口给运算符重载使用void _LogMessage(int level,char* rightbuffer){char leftbuffer[1024];string levelstr = LevelToString(level);string currtime = GetTime();string idstr = to_string(getpid());snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str(),currtime.c_str(),idstr.c_str());string messages = leftbuffer;messages+=rightbuffer;WriteLog(levelstr,messages);}//运算符重载void operator()(int level,const char* format,...){char rightbuffer[1024];va_list args;   //va_list 是指针va_start(args,format);//初始化va_list对象,format是最后一个确定的参数vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中va_end(args);_LogMessage(level,rightbuffer);}~Log() {}
private:int _style;string _filename;
};

 Main.cc

#include "Log.hpp"int main()
{Log log;log.Enable(ClassFile);log(Debug, "%d %s %f", 10, "test", 3.14);log(Info, "%d %s %f", 10, "test", 3.14);log(Warnig, "%d %s %f", 10, "test", 3.14);log(Error, "%d %s %f", 10, "test", 3.14);log(Fatal, "%d %s %f", 10, "test", 3.14);log(Debug, "%d %s %f", 10, "test", 3.14);log(Info, "%d %s %f", 10, "test", 3.14);log(Warnig, "%d %s %f", 10, "test", 3.14);log(Error, "%d %s %f", 10, "test", 3.14);log(Fatal, "%d %s %f", 10, "test", 3.14);log(Debug, "%d %s %f", 10, "test", 3.14);log(Info, "%d %s %f", 10, "test", 3.14);log(Warnig, "%d %s %f", 10, "test", 3.14);log(Error, "%d %s %f", 10, "test", 3.14);log(Fatal, "%d %s %f", 10, "test", 3.14);
}

数据就被我们分门别类的写入到了文件中,后面查询起来就很方便。 

二、线程池

之前我们还学习过生产者消费者模型,创建一批线程去生产,还有一批线程去消费。线程池也类似于如此。只不过生产是主线程自己做。有了任务他只需要将内容分配给已经被创建好进程,让这些进程去消费就行了,而这些被提前创建好的进程,就叫做进程池

1.线程池基本原理

  • 线程池由一组预先创建的线程组成,这些线程等待接收并执行任务。
  • 当需要执行任务时,而且线程池中有空闲线程时,任务被分配给其中一个空闲线程执行。
  • 如果没有空闲线程,任务将被放入任务队列中等待执行,直到有线程空闲为止。

2.线程池作用 

  • 降低了线程创建和销毁的开销:线程的创建和销毁是比较昂贵的操作,线程池可以避免频繁地创建和销毁线程。
  • 提高了性能:通过重用线程,可以减少线程的上下文切换和内存占用,提高了系统的整体性能。
  • 控制并发度:可以限制并发执行的任务数量,防止系统资源被耗尽。

3.线程池的实现 

我们需要如下变量:

  1. 一个任务队列,用于存储待执行的任务。
  2. 一个容器,来存放线程。
  3. 整形变量:存放线程最多的数量
  4. 保护临界资源:互斥锁
  5. 通知线程去处理任务:条件变量

具体代码如下

LockGuard.hpp   不生产锁,做线程的守护者,作用域到了自动释放锁

#pragma once
#include <pthread.h>// 不定义锁,外部会传递锁
class Mutex
{
public:Mutex(pthread_mutex_t *lock): _lock(lock){}void Lock(){pthread_mutex_lock(_lock);}void UnLock(){pthread_mutex_unlock(_lock);}~Mutex(){}private:pthread_mutex_t *_lock;
};class LockGuard
{
public:LockGuard(pthread_mutex_t *lock): _mutex(lock){_mutex.Lock();}~LockGuard(){_mutex.UnLock();}
private:Mutex _mutex;
};

 Thread.hpp   模拟C++实现的线程,封装了一下

#pragma once#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>// 设计方的视角
//typedef std::function<void()> func_t;
template<class T>
using func_t = std::function<void(T&)>;template<class T>
class Thread
{
public:Thread(const std::string &threadname, func_t<T> func, T &data):_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data){}// 不加static会有this指针,无法调用pthread_creadtestatic void *ThreadRoutine(void *args) // 类内方法,{// (void)args; // 仅仅是为了防止编译器有告警Thread *ts = static_cast<Thread *>(args);ts->_func(ts->_data);return nullptr;}//运行线程bool Start(){int n = pthread_create(&_tid, nullptr, ThreadRoutine, this/*?*/);if(n == 0) {_isrunning = true;return true;}else return false;}//等待线程bool Join(){if(!_isrunning) return true;int n = pthread_join(_tid, nullptr);if(n == 0){_isrunning = false;return true;}return false;}std::string ThreadName(){return _threadname;}bool IsRunning(){return _isrunning; }~Thread(){}
private:pthread_t _tid;std::string _threadname;bool _isrunning;func_t<T> _func;T _data;
};

 Log.hpp   前面写的日志

#pragma once#include<iostream>
#include<cstdarg>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
using namespace std;
enum{Debug = 0,Info,Warnig,Error,Fatal
};enum{Screen = 10,OneFile,ClassFile
};string LevelToString(int level)
{switch (level){case Debug:return "Debug";case Info:return "Info";case Warnig:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "Unkonw"; }
}const int default_style = Screen;
const string default_filename = "Log.";
const string logdir = "log";class Log  
{
public:Log(int style = default_style,string filename = default_filename):_style(style),_filename(filename){mkdir(logdir.c_str(),0775);}//更改打印方式void Enable(int style){_style = style;}//时间戳转化为年月日时分秒string GetTime(){time_t currtime = time(nullptr);struct tm* curr = localtime(&currtime);char time_buffer[128];snprintf(time_buffer,sizeof(time_buffer),"%d-%d-%d %d:%d:%d",curr->tm_year+1900,curr->tm_mon+1,curr->tm_mday,curr->tm_hour,curr->tm_min,curr->tm_sec);return time_buffer;}//写入到文件中void WriteLogToOneFile(const string& logname,const string& message){FILE* fp = fopen(logname.c_str(),"a");if(fp==nullptr){perror("fopen filed");exit(-1);}fprintf(fp, "%s\n", message.c_str());fclose(fp);}//打印日志void WriteLogToClassFile(const string& levelstr,const string& message){string logname = logdir;logname+="/";logname+=_filename;logname+=levelstr;WriteLogToOneFile(logname,message);}void WriteLog(const string& levelstr,const string& message){switch (_style) {case Screen:cout<<message<<endl;//打印到屏幕中break;case OneFile:WriteLogToClassFile("all",message);//给定all,直接写到all里break;case ClassFile:WriteLogToClassFile(levelstr,message);//写入levelstr里break;default:break;}}//打印日志void LogMessage(int level,const char* format,...){char rightbuffer[1024];//处理消息va_list args;   //va_list 是指针va_start(args,format);//初始化va_list对象,format是最后一个确定的参数//现在args指向了可变参数部分vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中va_end(args);char leftbuffer[1024];//处理日志等级、pid、时间string levelstr = LevelToString(level);string currtime = GetTime();string idstr = to_string(getpid());snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str(),currtime.c_str(),idstr.c_str());string loginfo = leftbuffer;loginfo+=rightbuffer;WriteLog(levelstr,loginfo);}//提供接口给运算符重载使用void _LogMessage(int level,char* rightbuffer){char leftbuffer[1024];string levelstr = LevelToString(level);string currtime = GetTime();string idstr = to_string(getpid());snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str(),currtime.c_str(),idstr.c_str());string messages = leftbuffer;messages+=rightbuffer;WriteLog(levelstr,messages);}//运算符重载void operator()(int level,const char* format,...){char rightbuffer[1024];va_list args;   //va_list 是指针va_start(args,format);//初始化va_list对象,format是最后一个确定的参数vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中va_end(args);_LogMessage(level,rightbuffer);}~Log() {}
private:int _style;string _filename;
};Log lg;class Conf
{
public:Conf(){lg.Enable(ClassFile); }~Conf(){}
};Conf conf;

 ThreadPool.hpp   线程池的实现

#pragma once#include <pthread.h>
#include <vector>
#include <functional>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "LockGuard.hpp"
using namespace std;static const int default_num = 5;class ThreadData
{
public:ThreadData(string name):thread_name(name){}string thread_name;
};template <class T>
class ThreadPool
{
public:ThreadPool(int thread_num = default_num): _thread_num(thread_num){pthread_mutex_init(&_mutex, nullptr); // 初始化pthread_cond_init(&_cond, nullptr);// 创建指定个数的线程for (int i = 0; i < _thread_num; i++){string thread_name = "thread_";thread_name += to_string(i + 1);ThreadData td(thread_name);//ThreadData为线程数据类型Thread<ThreadData> t(thread_name, bind(&ThreadPool<T>::ThreadRun, this, placeholders::_1), td);_threads.emplace_back(t);lg(Info,"%s 被创建...",thread_name.c_str());//写入}}//线程运行bool Start(){for (auto &thread : _threads){thread.Start();lg.LogMessage(Info,"%s 正在运行!",thread.ThreadName().c_str());}}//线程条件变量等待void ThreadWait(ThreadData &td){lg.LogMessage(Debug,"没有任务,%s休眠了",td.thread_name.c_str());pthread_cond_wait(&_cond,&_mutex);}//线程条件变量唤醒void ThreadWakeUp(){pthread_cond_signal(&_cond);}//执行任务void ThreadRun(ThreadData &td){while (1){T t;// 取出任务{LockGuard lockguard(&_mutex);//代码块中自动加锁与解锁while (_q.empty()){ThreadWait(td);lg.LogMessage(Debug,"有任务了,%s去执行任务了",td.thread_name.c_str());}t = _q.front();_q.pop();}//处理任务 我们通过打印消息来模拟任务// cout<<t<<endl;lg.LogMessage(Debug,"%s 计算结果为:%d",td.thread_name.c_str(),t);}}//将任务放到队列中void Push(const T &in){{LockGuard lockguard(&_mutex);_q.push(in);}lg.LogMessage(Debug,"任务push成功,任务是: %d",in);ThreadWakeUp();}~ThreadPool(){pthread_mutex_destroy(&_mutex); // 销毁pthread_cond_destroy(&_cond);}//进程等待void Wait(){for (auto &thread : _threads){thread.Join();}}private:queue<T> _q;vector<Thread<ThreadData>> _threads;int _thread_num;pthread_mutex_t _mutex;pthread_cond_t _cond;
};

 Main.cc

#include <memory>
#include "Threadpool.hpp"int main()
{unique_ptr<ThreadPool<int>> tp(new ThreadPool<int>());tp->Start();int i = 1;while(true){//放入数据模拟任务tp->Push(i++);sleep(1);}tp->Wait();return 0;
}

执行后,就将内容写入到了日志里面。 


http://www.ppmy.cn/server/7133.html

相关文章

2024年Getx教程_Flutter+Getx系列实战教程介绍

Getx介绍&#xff1a; GetX 是 Flutter 上的一个轻量且强大的解决方案&#xff0c;它是一个轻量级的Flutter框架。Getx不仅具有状态管理的功能&#xff0c;还具有路由管理、主题管理、国际化多语言管理、Obx局部更新、MVC视图业务分离、网络请求、数据验证等功能。GetX 官方还…

6.6Python之集合的基本语法和特性

集合&#xff08;Set&#xff09;是Python中的一种无序、不重复的数据结构。集合是由一组元素组成的&#xff0c;这些元素必须是不可变数据类型&#xff0c;但在集合中每个元素都是唯一的&#xff0c;即集合中不存在重复的元素。 集合的基本语法&#xff1a; 1、元素值必须是…

IDEA插件:CodeGeex

前言 CodeGeeX是由清华大学和智谱AI联合开发的多语言代码生成模型。CodeGeeX是一款AI编程助手&#xff0c;其功能类似于Github Copilot、Codeium、CodeWhisperer、Bito等智能编程助手。CodeGeeX支持Python、C、Java、JavaScript、Go等10多种主流编程语言。它可以帮助程…

JDK 11下载、安装、配置

下载 到Oracle管网下载JDK 11&#xff0c;下载前需要登录&#xff0c;否则直接点下载会出现502 bad gateway。 下载页面链接 https://www.oracle.com/hk/java/technologies/downloads/#java11-windows 登录 有些人可能没有Oracle账号&#xff0c;注册也比较慢&#xff0c;有需…

边缘计算网关有哪些用途及使用方法?-天拓四方

在数字化日益深入的今天&#xff0c;边缘计算网关作为一种重要的设备&#xff0c;正在越来越多地被应用于各种场景中。它不仅能够提升数据处理的速度和效率&#xff0c;还能在降低网络延迟的同时确保数据的安全性。本文将详细介绍边缘计算网关的用途及其使用方法&#xff0c;帮…

excel文件预览: luckyexcel+luckysheet

luckyexcel 使用 npm i luckyexcel --saveluckysheet 使用 luckysheet文档 克隆Luckysheet源码到本地 git clone https://github.com/dream-num/Luckysheet.git安装依赖 npm install npm install gulp -g 开发 npm run dev打包 npm run build本地引入 npm run build 后 …

代码随想录训练营Day 24|Python|Leetcode|93.复原IP地址, 78.子集,90.子集II

93.复原IP地址 有效 IP 地址 正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能含有前导 0&#xff09;&#xff0c;整数之间用 . 分隔。 例如&#xff1a;"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址&#xff0c;但是 &q…

Python 求矩阵的局部极大值

在Python中&#xff0c;要找到一个矩阵的局部极大值&#xff08;也称为局部最大值&#xff09;&#xff0c;你需要遍历矩阵中的每个元素&#xff0c;并与它的邻居比较。局部极大值是指一个元素的值大于其所有相邻元素的值。 以下是一个简单的Python函数&#xff0c;它接受一个…