目录
前言
一、日志的编写
二、线程池
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.线程池的实现
我们需要如下变量:
- 一个任务队列,用于存储待执行的任务。
- 一个容器,来存放线程。
- 整形变量:存放线程最多的数量
- 保护临界资源:互斥锁
- 通知线程去处理任务:条件变量
具体代码如下
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;
}
执行后,就将内容写入到了日志里面。