文章目录
本文代码已提交在我的github仓库中
一、死锁问题
死锁是指在一组线程中的线程均占有不会释放的资源,但因互相申请被其他线程锁占有不会释放的资源而处于的一种永久等待状态。

假设线程1和线程2都同时需要锁A和锁B,申请一把锁是原子的,但是申请两把锁就不是了。有可能造成线程1拿着锁A阻塞等待锁B,线程2拿着锁B阻塞等待锁A,这就是死锁问题了!
死锁有四个必要条件:
- 互斥:资源独占,别的线程不能用
- 请求与保持:拿着锁,又去申请新锁
- 不可剥夺:锁不能被强行抢走
- 循环等待:A等B,B等A,形成环
只要破坏任意一个条件,就能避免死锁了。
二、日志功能的实现
计算机中的日志是指记录系统和软件运行中发生事件的文件,主要作用是监控运行状态、记录异常信息,帮助快速定位问题并支持程序员进行问题修复。日志是系统维护、故障排查、安全管理的重要工具。
在日志中必须有以下指标:
- 时间
- 日志等级(问题的严重程度)
- 日志内容
也可以有:
- 进程/线程信息
- 文件名、行号
我们想要的日志格式为:[时间][日志等级][进程id][文件名][代码行号] - 内容信息
市面上已经有了很多成熟的日志方案,今天我们还是自己写一个:采用设计模式------策略模式进行设计。
日志可以有多种刷新策略:打印到显示器、写入文件、写入不同文件...
策略模式就可以让我们把不同的"做事方法"单独封装成独立策略类,让它们可以互相替换,程序运行时动态切换方法,不用修改原有代码。
cpp
// Logger.hpp
#pragma once
#include "Mutex.hpp"
#include <cstdio>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
// 日志等级
enum class LogLevel
{
INFO,
WARNING,
ERROR,
FATAL,
};
std::string LogLevel2String(LogLevel level)
{
switch (level)
{
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
default:
return "UNKNOWN";
}
}
// 刷新策略的基类
class LogStrategy
{
public:
virtual ~LogStrategy()
{}
// 写日志的具体方法,每种策略中必须重写该函数
virtual void SynLog(const std::string& message) = 0;
};
// 显示器打印日志策略
class ConsoleStrategy : public LogStrategy
{
public:
void SynLog(const std::string& message) override
{
LockGuard lg(_mutex);
std::cout << message << std::endl;
}
private:
Mutex _mutex;
};
// 文件写入日志策略
const std::string default_logpath = "./log";
const std::string default_logfilename = "log.txt";
class FileStrategy : public LogStrategy
{
public:
FileStrategy(const std::string& logpath = default_logpath, const std::string& logfilename = default_logfilename)
: _logpath(logpath), _logfilename(logfilename)
{
// 如果传入的目录和文件不存在则创建
LockGuard lg(_mutex);
// C++17引入的std::filesystem库用法
if (std::filesystem::exists(_logpath))
return;
try
{
std::filesystem::create_directories(_logpath);
}
catch (const std::filesystem::filesystem_error& e)
{
std::cerr << e.what() << std::endl;
}
}
void SynLog(const std::string& message) override
{
LockGuard lg(_mutex);
if (_logpath != "" && _logpath.back() != '/')
{
_logpath += '/';
}
std::string targetlog = _logpath + _logfilename;
std::ofstream out(targetlog, std::ios::app);
if (!out.is_open())
{
std::cerr << "open " << targetlog << " fail!" << std::endl;
return;
}
out << message << '\n';
out.close();
}
private:
std::string _logpath; // 日志文件所在目录
std::string _logfilename; // 日志文件名
Mutex _mutex;
};
// 按等级写入不同文件策略
class FileLevelStrategy : public LogStrategy
{
public:
FileLevelStrategy(const std::string& logpath = default_logpath) : _logpath(logpath)
{
// 如果传入的目录不存在则创建
LockGuard lg(_mutex);
// C++17引入的std::filesystem库用法
if (std::filesystem::exists(_logpath))
return;
try
{
std::filesystem::create_directories(_logpath);
}
catch (const std::filesystem::filesystem_error& e)
{
std::cerr << e.what() << std::endl;
}
}
void SynLog(const std::string& message) override
{
LockGuard lg(_mutex);
if (_logpath != "" && _logpath.back() != '/')
{
_logpath += '/';
}
auto it = std::find(message.begin() + 1, message.end(), '[');
if(it != message.end() && it + 1 != message.end())
{
char level = *(it + 1);
if(level == 'I')
{
_logfilename = "log.Info.txt";
}
else if(level == 'W')
{
_logfilename = "log.Warning.txt";
}
else if(level == 'E')
{
_logfilename = "log.Error.txt";
}
else if(level == 'F')
{
_logfilename = "log.Fatal.txt";
}
else
{
_logfilename = "log.Unknown.txt";
}
}
std::string targetlog = _logpath + _logfilename;
std::ofstream out(targetlog, std::ios::app);
if (!out.is_open())
{
std::cerr << "open " << targetlog << " fail!" << std::endl;
return;
}
out << message << '\n';
out.close();
}
private:
std::string _logpath;
std::string _logfilename;
Mutex _mutex;
};
// 获取当前时间方法
std::string GetTime()
{
struct timeval cur_time;
gettimeofday(&cur_time, nullptr);
struct tm struct_time;
localtime_r(&(cur_time.tv_sec), &struct_time);
char timestr[128];
snprintf(timestr, sizeof timestr, "%04d-%02d-%02d %02d:%02d:%02d", struct_time.tm_year + 1900,
struct_time.tm_mon + 1, struct_time.tm_mday, struct_time.tm_hour, struct_time.tm_min, struct_time.tm_sec);
return timestr;
}
// 日志类需要完成: 1.生成日志信息 2.根据不同的策略进行刷新
class Logger
{
private:
std::unique_ptr<LogStrategy> _strategy; // 刷新策略
public:
// 内部类,描述一条完整的日志信息:
// [时间][日志等级][进程id][文件名][代码行号] - 内容信息
class LogMessage
{
public:
LogMessage(LogLevel level, int line, std::string& filename, Logger& logger)
: _cur_time(GetTime()), _level(level), _pid(getpid()), _filename(filename), _line(line), _logger(logger)
{
std::stringstream ss;
ss << '[' << _cur_time << ']' << '[' << LogLevel2String(_level) << ']' << '[' << _pid << ']' << '['
<< _filename << ']' << '[' << _line << ']' << " - ";
_loginfo = ss.str();
}
// 读取用户输入的内容信息
template <class T> LogMessage& operator<<(const T& info)
{
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
// RAII自动刷新
~LogMessage()
{
_logger._strategy->SynLog(_loginfo);
}
private:
std::string _cur_time;
LogLevel _level;
pid_t _pid;
std::string _filename;
int _line;
std::string _loginfo;
Logger& _logger;
};
public:
Logger()
{
// C++14用法
_strategy = std::make_unique<ConsoleStrategy>();
}
void UseConsoleStrategy()
{
_strategy = std::make_unique<ConsoleStrategy>();
}
void UseFileStrategy()
{
_strategy = std::make_unique<FileStrategy>();
}
void UseFileLevelStrategy()
{
_strategy = std::make_unique<FileLevelStrategy>();
}
LogMessage operator()(LogLevel level, std::string filename, int line)
{
return LogMessage(level, line, filename, *this);
}
};
Logger logger;
#define USE_CONSOLE_LOG_STRATEGY() logger.UseConsoleStrategy();
#define USE_FILE_LOG_STRATEGY() logger.UseFileStrategy();
#define USE_FILE_LEVEL_LOG_STRATEGY() logger.UseFileLevelStrategy();
#define LOG(level) logger(level, __FILE__, __LINE__)
// 至此,我们就可以用 "LOG(level) << 日志信息" 的方式进行日志写入刷新了
测试:



三、单例模式的线程池
我们之前已经写过了进程池。
所谓池化技术,就是提前批量创建一批资源放到「池子」里统一管理,需要用时直接从池子借用,用完归还,不销毁;避免频繁创建 / 销毁昂贵资源,以此节省开销、提升性能、控制并发。
我们的线程池引入单例模式的使用:指的是只允许在加载或运行时,整体最多创建一个对象的模式。最佳实践是:在运行时创建对象(懒汉模式)
cpp
// ThreadPool.hpp
#pragma once
#include "Cond.hpp"
#include "Logger.hpp"
#include "Mutex.hpp"
#include "Thread.hpp"
#include <pthread.h>
#include <queue>
#include <unistd.h>
#include <vector>
const int defaultnum = 5;
template <class T>
class ThreadPool
{
private:
void HandlerTask()
{
char name[128];
pthread_getname_np(pthread_self(), name, sizeof name);
while (1)
{
T task;
{
LockGuard lg(_mutex);
// 如果线程池还在运行,但是没有任务,就阻塞等待任务
while (_tasks.empty() && _isRunning)
{
_cond.Wait(_mutex);
}
// 如果线程池不运行,且没有任务了,就退出
if (!_isRunning && _tasks.empty())
{
_mutex.Unlock();
break;
}
task = _tasks.front();
_tasks.pop();
}
task();
LOG(LogLevel::INFO) << name << "处理任务: " << task.Result();
sleep(1);
}
}
public:
ThreadPool(int thread_num = defaultnum) : _thread_num(thread_num), _isRunning(false)
{
for (int i = 0; i < _thread_num; i++)
{
_threads.emplace_back([this]() { this->HandlerTask(); });
}
}
void Start()
{
_isRunning = true;
for (auto& thread : _threads)
{
thread.Start();
}
}
void Stop()
{
LockGuard lg(_mutex);
_isRunning = false;
_cond.BroadCast();
}
void Wait()
{
for (auto& thread : _threads)
{
thread.Join();
}
}
void Enqueue(T in)
{
LockGuard lg(_mutex);
_tasks.push(in);
_cond.Signal();
}
public:
// 单例模式,全局只有一个线程池对象
// 确保只有一个线程创建对象,双层if判断保护线程安全
static ThreadPool<T>* Instance()
{
if (_instance == nullptr)
{
LockGuard lg(_lock);
if (_instance == nullptr)
{
_instance = new ThreadPool<T>();
_instance->Start();
LOG(LogLevel::INFO) << "第一次使用线程池,创建线程池对象";
}
}
return _instance;
}
private:
static ThreadPool<T>* _instance;
static Mutex _lock;
private:
int _thread_num;
std::vector<Thread> _threads;
std::queue<T> _tasks; // 任务队列,临界资源
bool _isRunning;
Mutex _mutex;
Cond _cond;
};
template <class T>
ThreadPool<T>* ThreadPool<T>::_instance = nullptr;
template <class T>
Mutex ThreadPool<T>::_lock;
测试:


本篇完,感谢阅读。