目录
前言
学了很多线程相关的知识点,线程控制、线程互斥、线程同步,今天我们将他们做一个总结,运用所学知识写一个较为完整的线程池,同时把日志编写也学一下。
一、日志的编写
在企业开发过程中,经常会通过打印日志来查看当前项目的运行情况。写一个日志难度并不大,用到的都是之前学的知识,注释写得比较详细,代码如下。
Log.hpp
cpp
#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;
};
cpp
#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 不生产锁,做线程的守护者,作用域到了自动释放锁
cpp
#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++实现的线程,封装了一下
cpp
#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_creadte
static 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 前面写的日志
cpp
#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 线程池的实现
cpp
#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;
};
cpp
#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;
}
执行后,就将内容写入到了日志里面。