这里写自定义目录标题
- 日志
- Log.hpp
- 测试main.cpp
- 结果
- 线程池
- 线程池的种类
- ThreadPool.hpp
- 测试 Task.hpp 和 main.cpp
- Task.hpp
- main.cpp
- 结果
- 线程池的单例模式
- 实现方式
- SignalThreadPool.hpp
- 测试main.cpp
- 线程安全与重入问题
- 死锁
- 死锁的四个必要条件
日志
日志需要包含的信息
• 时间(戳)
• ⽇志等级
• ⽇志内容
以下⼏个指标是可选的
• ⽂件名⾏号
• 进程,线程相关id信息等
要实现的日志的格式
[可读性很好的时间] [⽇志等级] [进程pid] [打印对应⽇志的⽂件名][⾏号] - 消息内容,⽀持可
变参数
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [17] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [18] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [20] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [21] - hello world
[2024-08-04 12:27:03] [WARNING] [202938] [main.cc] [23] - hello world
策略模式:
Log.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include <time.h>
#include <filesystem>
#include <fstream>
#include <sstream>
#include "Mutex.hpp"
namespace LogModule
{using namespace MutexModule;// 1.默认路径和文件名std::string default_path = "./log/";std::string default_filename = "log.txt";// 2.日志等级enum class LogLevel{DEBUG = 1,INFO,WARNING,ERROR,FATAL};//获得当前的时间std::string CurrTime(){time_t t = time(nullptr);struct tm cur_time;localtime_r(&t, &cur_time);char buff[64];snprintf(buff, sizeof(buff), "%4d-%02d-%02d %02d:%02d:%02d", cur_time.tm_year + 1900, cur_time.tm_mon + 1, cur_time.tm_mday, cur_time.tm_hour, cur_time.tm_min, cur_time.tm_sec);return buff;}//将相应的日志等级转换为字符串std::string LevelToString(LogLevel level){switch (level){case LogLevel::DEBUG:return "DEBUG";case LogLevel::WARNING:return "WARNING";case LogLevel::FATAL:return "FATAL";case LogLevel::INFO:return "INFO";case LogLevel::ERROR:return "ERROR";default:return "None";}}// 3.刷新策略//纯虚类class LogStrategy{public:virtual ~LogStrategy() {}virtual void SyncMessage(std::string message) {}};// 3.1显示器刷新//显示器刷新策略class ConsoleStrategy : public LogStrategy{public:ConsoleStrategy(){}~ConsoleStrategy(){}void SyncMessage(std::string message) override{// 向显示器上打印//显示器是临界资源需要加锁LockGuard lockguard(_mutex);std::cout << message << std::endl;}private:Mutex _mutex;};// 3.2文件刷新//文件刷新策略class FileStrategy : public LogStrategy{public:FileStrategy(std::string path = default_path, std::string filename = default_filename): _path(path),_filename(filename){std::string fullpath = _path + _filename;// 判断文件是否存在//文件资源也是临界资源,也需要加锁LockGuard lockguard(_mutex);if (std::filesystem::exists(fullpath)){return;}try{// 不存在就创建std::filesystem::create_directories(_path);}catch (std::filesystem::filesystem_error &e){std::cerr << e.what() << std::endl;}}~FileStrategy(){}void SyncMessage(std::string message) override{std::string fullpath = _path+_filename;// 日志写入一定是追加std::ofstream of(fullpath, std::ios_base::app);LockGuard lockguard(_mutex);of << message << std::endl;}private:std::string _filename;//文件名std::string _path;//文件路径Mutex _mutex;};// 4.日志类class Logger{public:Logger(): _strategy(new ConsoleStrategy()){}~Logger(){}//Logger类的内部类class LogMessage{public:LogMessage(LogLevel level, int line, std::string filename, Logger &logger): _logger(logger),_curr_time(CurrTime()),_level(level),_pid(getpid()),_filename(filename),_line(line){//[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world_info = "[" + _curr_time + "] " +"[" + LevelToString(_level) + "] " +"[" + std::to_string(_pid) + "] " +"[" + _filename + "] " +"[" + std::to_string(_line) + "] - ";}//<<重载的模板意味着可以接受任何类型template <class T>LogMessage &operator<<(T data){std::stringstream ss;ss << data;_info += ss.str();return *this;}~LogMessage(){_logger._strategy->SyncMessage(_info);}private:Logger &_logger; //应用logger可以访问Logger类的成员变量和方法了std::string _curr_time; // 当前时间LogLevel _level; // 日志等级pid_t _pid; // pidstd::string _filename; // 文件名int _line; // 行号std::string _info; // 日志信息};// LogMessage operator()(LogLevel level, int line, std::string filename, Logger &logger)//Logger类重载的()LogMessage operator()(LogLevel level, int line, std::string filename){LogMessage logmessage(level, line, filename, *this);return logmessage;}//开启显示器刷新策略void EnableConsole(){_strategy = std::make_shared<ConsoleStrategy>();}//开启文件刷新策略void EnableFile(){_strategy = std::make_shared<FileStrategy>();}private:std::shared_ptr<LogStrategy> _strategy; // 刷新策略};Logger logger;#define LOG(level) logger(level, __LINE__, __FILE__)
#define ENABLE_CONSOLE() logger.EnableConsole()
#define ENABLE_FILE() logger.EnableFile();
}
测试main.cpp
#include "Log.hpp"
using namespace LogModule;
int main()
{//ENABLE_FILE();ENABLE_CONSOLE();int cnt = 10;while (cnt){LOG(LogLevel::DEBUG) << "hello world " << cnt;cnt--;}return 0;
}
- 刷新的过程
LOG(LogLevel::DEBUG)本质上是logger(level, LINE, FILE),返回值是LogMessage的临时对象,LogMessage类中又重载了**<<**,所以信息被整合到LogMessage的成员变量_info中,最后调用Logger类中共享指针调用刷新策略来进行_info的刷新。
结果
- 显示器刷新策略
- 打开ENABLE_FILE(),就是文件刷新策略
线程池
线程池的种类
a. 创建固定数量线程池,循环从任务队列中获取任务对象,获取到任务对象后,执⾏任务对象中的任务接口
b. 浮动线程池,其他同上
此处,我们选择固定线程个数的
ThreadPool.hpp
#pragma once
#include <iostream>
#include <vector>
#include <queue>
#include <functional>
#include <unistd.h>
#include "thread.hpp"
#include "Log.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
#include "Task.hpp"
namespace ThreadPollModule
{using namespace ThreadModule;using namespace LogModule;using namespace MutexModule;using namespace CondModule;int default_thread_num = 5;//默认线程数量template <class T>class ThreadPool{using task_t = T;//T是要处理的任务类型,也是用户自己定义的bool IsEmpty(){return _task_q.empty();}void HandlerTask(std::string name){while (true){// 1.获取任务// 访问临界资源需要枷锁{//此时is_running是false,任务是有限的,每个线程都处理任务,直到没有任务,这是退出线程LockGuard lockguard(_mutex);while (IsEmpty()&&_is_running){_wait_num++;_cond.Wait(_mutex);_wait_num--;}if(IsEmpty()&&!_is_running)break;}// 2. 处理任务// 是同步的可以不加锁,这样效率更高task_t t = _task_q.front();_task_q.pop();t(); // 这里规定,用户传进来的任务对象需要重载()来表示任务}LOG(LogLevel::INFO)<<"退出线程"<<name<<"...成功";}public:ThreadPool(int thread_num = default_thread_num): _thread_num(thread_num),_is_running(false){// 创建线程for (int i = 0; i < _thread_num; i++){//绑定对象,因为HandlerTask是类内成员Thread t(std::bind(&ThreadPollModule::ThreadPool<T>::HandlerTask,this,std::placeholders::_1));LOG(LogLevel::INFO) << "创建线程"<<t.Name();_threads.push_back(t);}}~ThreadPool(){}//开启线程池void Start(){if(_is_running)return;_is_running = true;for (auto &e : _threads){LOG(LogLevel::INFO) << "启动线程"<<e.Name();e.Start();}}//回收所有线程void Wait(){for (auto &e : _threads){e.Join();LOG(LogLevel::INFO) << "回收线程" << e.Name()<<"...成功";}}//关闭线程池void Stop(){if(!_is_running)return;_is_running = false;_cond.NotifyAll();}//将任务入任务队列void Equeue(const task_t &t){if(!_is_running)return;LOG(LogLevel::INFO)<<"发送一个任务";LockGuard lockguard(_mutex);_task_q.push(t);if (_wait_num > 0)_cond.Notify();}private:bool _is_running; //线程池是否工作int _thread_num; // 线程数量int _wait_num; // 等待的线程数量std::vector<Thread> _threads; // 线程std::queue<task_t> _task_q; // 任务队列Mutex _mutex; // 锁Cond _cond;};
}
需要注意的是停止线程池时,对线程退出线程的理解。
stop:需要的条件
- 线程自己退出(要唤醒所有线程)
- 历史的任务都被处理完了
- 不能再入任务
测试 Task.hpp 和 main.cpp
Task.hpp
#pragma once
#include <iostream>class Task
{
public://任务void operator()(){std::cout<<"这是一个Task,我正在处理一个任务"<<std::endl;}
private:
};
main.cpp
#include "ThreadPool.hpp"
#include "Task.hpp"
using namespace ThreadPollModule;
int main()
{Task t;ThreadPool<Task> tp;int cnt = 5;tp.Start();while(cnt){tp.Equeue(t);sleep(1);cnt--;}tp.Stop();tp.Wait();return 0;
}
结果
先创建,再启动,之后就是处理任务,最后退出线程和回收线程。
线程池的单例模式
- 某些类, 只应该具有⼀个对象(实例), 就称之为单例.
实现方式
- 饿汉模式:提前准备
template <typename T>
class Singleton {
static T data;//直接就初始化好了
public:
static T* GetInstance() {
return &data;
}
};
- 懒汉模式:等到用的时候,再做相应的准备工作
比如:缺页中断,一开始并没有申请相应的物理空间,只有使用时,发生中断,才会申请物理内存
template <typename T>
class Singleton {
static T* inst;//初始化为nullptr
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();//需要使用时,再创建
}
return inst;
}
};
懒汉模式的优点就是,资源利用率高,因为只有用到的时候才会申请,而不是,现在用不到的也申请出来。
- 不允许拷贝和赋值,构造函数设为私有
SignalThreadPool.hpp
#pragma once
#include <iostream>
#include <vector>
#include <queue>
#include <functional>
#include <unistd.h>
#include "thread.hpp"
#include "Log.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
#include "Task.hpp"
namespace ThreadPollModule
{using namespace ThreadModule;using namespace LogModule;using namespace MutexModule;using namespace CondModule;int default_thread_num = 5;template <class T>class ThreadPool{using task_t = T;public:static ThreadPool<T> *GetInstance(){if (instance == nullptr){LockGuard lockguard(mutex);if (instance == nullptr){instance = new ThreadPool<T>();}}return instance;}private:ThreadPool(const ThreadPool<T> &tp) = delete; // 禁止拷贝构造const ThreadPool<T> &operator=(const ThreadPool<T> &tp) = delete;// 构造设为私有ThreadPool(int thread_num = default_thread_num): _thread_num(thread_num),_is_running(false){// 创建线程for (int i = 0; i < _thread_num; i++){Thread t(std::bind(&ThreadPollModule::ThreadPool<T>::HandlerTask, this, std::placeholders::_1));LOG(LogLevel::INFO) << "创建线程" << t.Name();_threads.push_back(t);}}bool IsEmpty(){return _task_q.empty();}void HandlerTask(std::string name){while (true){// 1.获取任务// 访问临界资源需要枷锁{LockGuard lockguard(_mutex);while (IsEmpty() && _is_running){_wait_num++;_cond.Wait(_mutex);_wait_num--;}if (IsEmpty() && !_is_running)break;}// 2. 处理任务// 是同步的可以不加锁,这样效率更高task_t t = _task_q.front();_task_q.pop();t(); // 这里规定传进来的任务对象需要重载()来表示任务}LOG(LogLevel::INFO) << "回收线程" << name << "...成功";}public:~ThreadPool(){}void Start(){if (_is_running)return;_is_running = true;for (auto &e : _threads){LOG(LogLevel::INFO) << "启动线程" << e.Name();e.Start();}}void Wait(){for (auto &e : _threads){e.Join();LOG(LogLevel::INFO) << "等待线程" << e.Name() << "...成功";}}void Stop(){if (!_is_running)return;_is_running = false;_cond.NotifyAll();}void Equeue(const task_t &t){LOG(LogLevel::INFO) << "发送一个任务";LockGuard lockguard(_mutex);_task_q.push(t);if (_wait_num > 0)_cond.Notify();}private:bool _is_running; // 线程池是否工作int _thread_num; // 线程数量int _wait_num; // 等待的线程数量std::vector<Thread> _threads; // 线程std::queue<task_t> _task_q; // 任务队列Mutex _mutex; // 锁Cond _cond;static ThreadPool<T> *instance;static Mutex mutex;};template <class T>ThreadPool<T> *ThreadPool<T>::instance = nullptr;template <class T>Mutex ThreadPool<T>::mutex;}
测试main.cpp
#include "ThreadPool.hpp"
#include "Task.hpp"
using namespace ThreadPollModule;
int main()
{ThreadPool<Task>::GetInstance()->Start();int cnt = 5;while(cnt){//std::cout<<"发送一个任务"<<std::endl;Task t;ThreadPool<Task>::GetInstance()->Equeue(t);sleep(1);cnt--;}ThreadPool<Task>::GetInstance()->Stop();ThreadPool<Task>::GetInstance()->Wait();return 0;
}
线程安全与重入问题
- 线程安全:描述的是线程
- 重入:描述的是函数
什么情况下需要考虑该函数是否是可重入的?
多个线程访问时或当一个线程时,需要考虑信号可能导致的重入问题
死锁
死锁的四个必要条件
- 互斥条件
- 请求与保持
- 不剥夺
- 循环等待
避免死锁的方法就是破坏四个必要条件中的一个
- 尽量不使用锁
- 不保持
- 可以剥夺
- 避免循环等待