前面我们已经大致学习过线程相关的内容。线程的申请、控制、阻塞、销毁和内存一样,频繁地申请调用资源会导致大量的性能损失,而利用我们前面学过的"池化"技术就可以一定程度上缓解这个问题。
本期我们就来深入学习如何设计线程池,写出基本版本的线程池,并学习用单例模式优化线程池,并给出相关的代码。
代码已经上传至作者的个人gitee:楼田莉子/Linux学习喜欢请点个赞谢谢。
目录
[1. 单例模式 (Singleton)](#1. 单例模式 (Singleton))
[2. 工厂方法模式 (Factory Method)](#2. 工厂方法模式 (Factory Method))
[3. 抽象工厂模式 (Abstract Factory)](#3. 抽象工厂模式 (Abstract Factory))
[4. 建造者模式 (Builder)](#4. 建造者模式 (Builder))
[5. 适配器模式 (Adapter)](#5. 适配器模式 (Adapter))
[6. 桥接模式 (Bridge)](#6. 桥接模式 (Bridge))
[7. 组合模式 (Composite)](#7. 组合模式 (Composite))
[8. 装饰器模式 (Decorator)](#8. 装饰器模式 (Decorator))
[9. 外观模式 (Facade)](#9. 外观模式 (Facade))
[10. 策略模式 (Strategy)](#10. 策略模式 (Strategy))
[11. 观察者模式 (Observer)](#11. 观察者模式 (Observer))
[12. 责任链模式 (Chain of Responsibility)](#12. 责任链模式 (Chain of Responsibility))
[13. 命令模式 (Command)](#13. 命令模式 (Command))
[14. 迭代器模式 (Iterator)](#14. 迭代器模式 (Iterator))
[15. 状态模式 (State)](#15. 状态模式 (State))
[饿汉式(Eager Initialization)](#饿汉式(Eager Initialization))
[懒汉式(Lazy Initialization)](#懒汉式(Lazy Initialization))
设计模式
在写代码前,我们需要优先引入一个更高级的内容------设计模式。
概念
设计模式 是在软件设计中,针对常见问题的通用、可复用的解决方案。它们不是可以直接转换成代码的完成设计,而是描述如何在不同的场景下解决特定问题的模板或指南。
设计模式有以下几个关键特点:
-
经验复用:它们来源于众多优秀软件系统的实践总结。
-
语言中立:虽然我们讨论C++实现,但模式思想适用于任何面向对象语言。
-
层次适中:设计模式介于需求分析和具体实现之间,比数据结构、算法更抽象,比框架更通用。
-
提升沟通:开发者可以说"这里用单例模式",大家立即明白意图。
设计模式通常被分为三大类:
-
创建型模式:关注对象的创建过程,将对象的创建与使用分离,提升灵活性和复用性。
-
结构型模式:关注类和对象的组合,通过继承或组合构建更大的结构。
-
行为型模式:关注对象之间的职责分配和通信,描述对象如何协作完成单个对象无法完成的任务。
常见的设计模式介绍
创建型模式
1. 单例模式 (Singleton)
-
特点:确保一个类只有一个实例,并提供一个全局访问点。在C++中需要考虑线程安全、析构处理等问题。
-
场景:
-
全局唯一的资源管理器,如日志系统、配置管理器、线程池。
-
需要频繁实例化然后销毁的对象,如数据库连接池。
-
-
C++实现要点:将构造函数、拷贝构造、赋值操作符设为私有或删除,使用静态方法获取实例(C++11后的局部静态变量初始化是线程安全的)。
2. 工厂方法模式 (Factory Method)
-
特点:定义一个用于创建对象的接口,但让子类决定实例化哪一个类。它将对象的实例化延迟到子类,符合"开闭原则"。
-
场景:
-
一个类无法预知它需要创建的对象的类。
-
类希望由其子类来指定所创建的对象。
-
当创建对象需要复杂初始化过程,且此过程可能变化时。
-
3. 抽象工厂模式 (Abstract Factory)
-
特点:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。它是工厂方法的升级版,用于创建产品族。
-
场景:
-
系统要独立于它的产品的创建、组合和表示。
-
系统需要由多个产品系列中的一个来配置(例如,跨平台UI组件:为Windows和Linux分别创建Button、Text等系列产品)。
-
需要强调一系列相关产品对象的设计以便进行联合使用。
-
4. 建造者模式 (Builder)
-
特点:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。它适用于那些包含多个组成部分、且构造过程复杂的对象。
-
场景:
-
需要生成的产品对象有复杂的内部结构,且构造顺序重要。
-
希望隔离复杂对象的创建与使用,例如构建XML文档、SQL查询语句或复杂的游戏角色。
-
-
与工厂模式区别 :工厂模式关注整体对象的创建,而建造者模式关注分步创建,最后
getResult。
结构型模式
5. 适配器模式 (Adapter)
-
特点:将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。有两种实现方式:类适配器(通过多继承)和对象适配器(通过组合)。
-
场景:
-
想使用一个已有的类,但其接口不符合当前需求。
-
需要创建一个可复用的类,该类与一些不相关的类协同工作。
-
在C++中,经常用于封装第三方库的接口,隔离变化。
-
6. 桥接模式 (Bridge)
-
特点:将抽象部分与实现部分分离,使它们都可以独立地变化。它通过组合代替继承,避免继承层次爆炸。
-
场景:
-
不希望抽象和实现之间有一个固定的绑定关系。
-
抽象和实现都应当能够通过子类化独立扩展。
-
对抽象的实现部分的修改应对客户不产生影响。
-
-
例子:图形绘制程序,形状(Shape)抽象和绘制API(DrawAPI)分离,可以独立增加新形状和新绘制方式。
7. 组合模式 (Composite)
-
特点:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性(透明性)。
-
场景:
-
想表示对象的部分-整体层次结构。
-
希望客户忽略组合对象与单个对象的差异,统一使用。
-
-
例子:文件系统中的文件和目录,GUI容器控件(Panel可以包含Button、Text,也可以包含其他Panel)。
8. 装饰器模式 (Decorator)
-
特点:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式比生成子类更为灵活。它通过包装对象来实现功能扩展。
-
场景:
-
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
-
需要扩展一个类的功能,但子类化会导致类爆炸。
-
当不能采用继承进行扩展时(如类被
final修饰,但在C++11前没有此关键字,但可能设计上不允许继承)。
-
-
C++实现:通常通过基类指针/引用和组合来实现,注意析构函数的正确性。
9. 外观模式 (Facade)
-
特点:为子系统中的一组接口提供一个一致的界面,定义了一个高层接口,这个接口使得这一子系统更加容易使用。
-
场景:
-
为一个复杂子系统提供一个简单入口。
-
希望将系统分层,利用外观对象作为每层的入口点。
-
在重构中,外观模式常用来为遗留代码定义更清晰的接口。
-
-
例子 :编译器,只需调用
Compiler.compile(),背后可能涉及词法分析、语法分析、代码生成等多个子系统。
行为型模式
10. 策略模式 (Strategy)
-
特点:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可独立于使用它的客户而变化。
-
场景:
-
一个类在其操作中需要使用多种算法变体,使用条件分支(if-else)导致难以维护。
-
需要动态选择算法。
-
-
C++实现 :可以利用函数指针、
std::function,但经典的面向对象方式是通过抽象基类定义策略接口,具体策略类实现它。
11. 观察者模式 (Observer)
-
特点:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。也称为发布-订阅模式。
-
场景:
-
一个对象的改变需要同时改变其他对象,且不知道具体有多少对象待改变。
-
一个抽象模型有两个方面,其中一个方面依赖于另一个方面。
-
-
例子:MVC架构中,Model变化时通知View更新;事件驱动系统中的监听器。
12. 责任链模式 (Chain of Responsibility)
-
特点:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
-
场景:
-
有多个对象可以处理一个请求,哪个对象处理由运行时决定。
-
想在不明确指定接收者的情况下,向多个对象中的一个提交请求。
-
-
例子:日志框架中日志级别过滤(DEBUG -> INFO -> ERROR),Web框架中的中间件。
13. 命令模式 (Command)
-
特点:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
-
场景:
-
需要抽象出待执行的动作,以便支持撤销、重做、事务等操作。
-
需要将请求调用者与请求接收者解耦。
-
-
例子:GUI按钮点击(将点击动作封装为命令对象),多级撤销功能,任务队列。
14. 迭代器模式 (Iterator)
-
特点:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。C++ STL中迭代器的思想就是该模式的完美体现。
-
场景:
-
需要访问一个聚合对象的内容,而无需暴露其内部表示。
-
需要支持对聚合对象的多种遍历方式。
-
为遍历不同的聚合结构提供一个统一的接口。
-
15. 状态模式 (State)
-
特点:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。它将状态相关的行为局部化到具体的状态类中。
-
场景:
-
一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变其行为。
-
操作中包含庞大的多分支条件语句,且这些分支依赖于该对象的状态。
-
-
例子:TCP连接状态(Established, Listening, Closed),订单状态机。
总结
| 模式名称 | 特点 | 应用场景 |
|---|---|---|
| 创建型模式 | ||
| 单例模式 | 确保一个类只有一个实例,并提供全局访问点;需考虑线程安全。 | 全局唯一的资源管理器,如日志系统、配置管理器、数据库连接池。 |
| 工厂方法模式 | 定义创建对象的接口,由子类决定实例化哪个类;将实例化延迟到子类。 | 类无法预知需要创建的对象类;希望子类指定所创建的对象;复杂初始化过程需变化时。 |
| 抽象工厂模式 | 提供创建一系列相关或相互依赖对象的接口,无需指定具体类;用于产品族创建。 | 跨平台UI组件(如Windows/Linux的按钮、文本框);需强调一系列相关产品联合使用。 |
| 建造者模式 | 将复杂对象的构建与表示分离,相同构建过程可创建不同表示;分步构造。 | 对象内部结构复杂且构造顺序重要,如构建XML文档、SQL查询、游戏角色。 |
| 结构型模式 | ||
| 适配器模式 | 将一个类的接口转换成客户期望的另一个接口,使不兼容的类能协同工作。 | 想用已有类但接口不匹配;封装第三方库接口,隔离变化。 |
| 桥接模式 | 将抽象部分与实现部分分离,使它们可独立变化;通过组合代替继承。 | 抽象和实现都需要扩展且避免继承爆炸,如形状与绘制API分离。 |
| 组合模式 | 将对象组合成树形结构表示"部分-整体"层次,使单个对象和组合对象使用一致。 | 文件系统(文件与目录)、GUI容器控件(Panel内可包含其他控件)。 |
| 装饰器模式 | 动态给对象添加额外职责,比子类化更灵活;通过包装对象扩展功能。 | 需要透明地给单个对象添加功能,且避免类爆炸,如Java I/O流包装。 |
| 外观模式 | 为子系统提供统一的高层接口,使子系统更易使用;简化复杂系统入口。 | 为复杂子系统(如编译器)提供简单入口;分层系统定义层入口;重构遗留代码。 |
| 行为型模式 | ||
| 策略模式 | 定义一系列算法,封装并使它们可相互替换;算法独立于使用它的客户。 | 类中有多种算法变体且需动态选择,如压缩算法、支付方式选择。 |
| 观察者模式 | 定义对象间一对多依赖,当主题状态变化时,所有观察者自动收到通知。 | MVC架构中Model变化通知View更新;事件驱动系统中的监听器。 |
| 责任链模式 | 使多个对象有机会处理请求,避免发送者与接收者耦合;请求沿链传递直至被处理。 | 日志级别过滤(DEBUG→INFO→ERROR);Web框架中间件;审批流程。 |
| 命令模式 | 将请求封装为对象,从而可用不同请求对客户参数化,支持请求排队、日志、撤销。 | GUI按钮点击封装为命令;多级撤销;任务队列;事务操作。 |
| 迭代器模式 | 提供顺序访问聚合对象元素的方法,而不暴露内部表示。 | 遍历集合(如STL迭代器);为不同聚合结构提供统一遍历接口。 |
| 状态模式 | 允许对象在内部状态改变时改变行为,仿佛修改了类;将状态相关行为局部化。 | 对象行为依赖状态且有大量条件分支,如TCP连接状态、订单状态机。 |
日志
在学习使用线程池前,我们必须要先实现一个日志内容。否则很难查询到bug并修复。
概念
日志 是程序在运行过程中,按照一定格式输出的、记录程序状态和事件的文本信息。它就像飞机的飞行记录仪,记录了软件运行的轨迹。
日志的主要作用包括:
-
故障排查:当程序崩溃或行为异常时,通过日志回溯上下文。
-
系统监控:实时观察程序运行状态(如吞吐量、错误率)。
-
性能分析:记录关键操作的耗时,发现性能瓶颈。
-
安全审计:记录用户操作、权限变更等敏感行为,满足合规需求。
-
业务统计:从日志中提取用户行为数据,辅助运营决策。
日志的组成部分
一个好的日志记录应该包含足够的上下文信息,以便快速定位问题。以下是日志的"黄金五要素",也是我多年实践中认为不可或缺的部分:
| 组成部分 | 说明 | 示例 |
|---|---|---|
| 时间戳 | 精确到毫秒或微秒的日期时间,有时带时区 | 2025-03-21 14:30:45.123 |
| 日志级别 | 标识日志的重要性和紧急程度 | INFO, ERROR, DEBUG |
| 线程ID | 在多线程程序中标识产生日志的线程 | Thread-42 或 0x7f8c4bfff700 |
| 源文件位置 | 文件名和行号,方便定位代码位置 | server.cpp:128 |
| 日志消息 | 描述事件的具体内容,通常带有格式化参数 | User login: username=alice |
除了这些,现代日志系统还会包含:
-
进程ID:区分不同进程的日志。
-
模块/组件名称:快速过滤特定模块的日志。
-
键值对/结构化字段 :方便机器解析,如
user_id=12345,latency=200ms。 -
栈追踪:在ERROR级别记录异常时的调用栈。
日志的内容应根据日志级别有所区分,每个级别承载不同的信息量:
-
FATAL (致命错误):导致程序无法继续运行的灾难性错误,记录后程序通常会退出。
-
ERROR (错误):影响功能正常执行的错误,但程序可以继续运行。必须包含错误码或描述,便于修复。
-
WARNING (警告):潜在的问题或不期望的情况,但程序仍能运行。例如:配置文件缺失,使用默认值。
-
INFO (信息):重要的运行时事件,如系统启动、用户登录、定时任务执行完成。生产环境通常默认开启INFO级别。
-
DEBUG (调试):开发或测试阶段开启的详细信息,记录变量值、函数调用流程、中间结果等。
-
TRACE (跟踪):比DEBUG更细粒度的信息,用于跟踪代码执行路径,通常只在性能分析时开启。
内容设计的黄金法则 :确保每个日志都能回答 "发生了什么?" 、"在哪个上下文中?" 、"严重程度如何?"。例如,好的日志:"ERROR [Thread-15] [auth.cpp:120] Failed to authenticate user 'john' -- invalid password." 坏的日志:"Failed to authenticate."
常见的日志库推荐
| 日志库 | 特点描述 | 适用场景 | 官方/仓库链接 |
|---|---|---|---|
| spdlog | 速度快,仅头文件(可选静态库),基于 fmt 格式化,支持异步日志、多后端输出(控制台、文件、轮转文件等),社区活跃 |
现代C++项目,尤其是对性能有要求的低延迟应用 | https://github.com/gabime/spdlog |
| glog | Google出品,稳定可靠,提供级别日志、条件日志、CHECK宏(类似assert)、自动文件切割 | 习惯Google风格的项目,或需要稳定基础日志功能的系统 | https://github.com/google/glog |
| Boost.Log | Boost库组件,功能极其强大灵活,支持层次化日志器、过滤器、格式器、接收器完全定制,可记录结构化数据 | 大型企业级应用,需要高度定制日志行为、多日志文件或远程日志 | https://www.boost.org/doc/libs/1_85_0/libs/log/doc/html/index.html |
| log4cxx | Apache log4j的C++移植,支持XML/属性文件配置、多种输出方式、日志分层,与Java日志生态相似 | 与Java系统混合部署,或有log4j经验的团队 | https://logging.apache.org/log4cxx/ |
| Easylogging++ | 仅头文件,配置极简,支持日志文件轮转、性能追踪、自定义格式,上手快 | 小型项目、快速原型、学习日志库 | https://github.com/amrayn/easyloggingpp |
| Poco Logger | Poco库的一部分,提供简单但够用的日志功能,支持通道(Channel)和格式化器 | 已经使用Poco库的应用程序 | https://github.com/pocoproject/poco |
| plog | 小巧、跨平台、仅头文件,注重性能与易用性,支持多输出和自定义格式 | 资源受限或对依赖敏感的项目 | https://github.com/SergiusTheBest/plog |
| log4cpp | log4j的另一个移植,比log4cxx轻量,但更新较慢 | 需要log4j风格但希望更轻量的项目 | https://sourceforge.net/projects/log4cpp/ |
日志的代码实现
信息等级集合
cpp
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR
};
时间戳函数
cpp
std::string GetCurrentDateTime()
{
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::tm tm_info; // 用户提供的缓冲区
localtime_r(&now_c, &tm_info); // POSIX 线程安全函数
std::ostringstream oss;
oss << std::put_time(&tm_info, "%Y-%m-%d %H:%M:%S");
return oss.str();
}
日志刷新策略
cpp
#pragma once
#include "Mutex.hpp"
#include <iostream>
#include <cstdio>
#include <string>
#include <memory>
#include <sstream>
#include <chrono>
#include <iomanip>
#include <ctime>
#include <unistd.h>
#include <filesystem> // C++17
#include <fstream>
namespace Logger
{
enum class LogLevel
{
INFO, ///< 信息性消息,记录应用程序的正常运行状态(如启动、配置加载等)
WARNING, ///< 警告,表示潜在的问题或非预期的情形,但应用程序仍能继续运行
ERROR, ///< 错误,表示发生了严重的操作失败,但不影响整个应用程序的继续运行
FATAL, ///< 致命错误,表示严重的故障,通常会导致应用程序终止
DEBUG ///< 调试信息,用于开发和排错阶段,记录详细的内部状态或流程
};
std::string GetCurrentDateTime()
{
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::tm tm_info; // 用户提供的缓冲区
localtime_r(&now_c, &tm_info); // POSIX 线程安全函数
std::ostringstream oss;
oss << std::put_time(&tm_info, "%Y-%m-%d %H:%M:%S");
return oss.str();
}
// 输出角度 -- 刷新策略
// 1. 显示器打印
// 2. 文件写入
// 日志的生成:
// 1. 构建日志字符串
// 2. 根据不同的策略,进行刷新
//策略模式接口
class LogStrategy
{
public:
virtual void LogRefresh(const std::string &message) = 0;
virtual ~LogStrategy() = default;
};
// 控制台日志刷新策略, 日志将来要向显示器打印
class ConsoleStrategy : public LogStrategy
{
public:
// 显示器打印策略刷新
void LogRefresh(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cerr << message << std::endl; // ??
}
~ConsoleStrategy()
{
}
private:
Mutex _mutex;
};
const std::string defaultpath = "./log";
const std::string defaultfilename = "log.txt";
namespace fs = std::filesystem;
// 文件策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &name = defaultfilename)
: _logpath(path),
_logfilename(name)
{
if (std::filesystem::exists(_logpath))
return;
try
{
std::filesystem::create_directories(_logpath);
}
catch(const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\n';
}
if(!_logpath.empty()&&_logpath.back() != '/')
_logpath += '/';
}
// 文件策略刷新
void LogRefresh(const std::string &message) override
{
{
std::string targetlogfile = _logpath + _logfilename;
LockGuard lockguard(_mutex);
std::ofstream logFile(targetlogfile, std::ios::app);// 以追加模式打开文件
if (!logFile.is_open())
{
std::cerr << "无法打开日志文件: " << targetlogfile << std::endl;
return;
}
logFile << message << "\n";
logFile.close();
}
}
~FileLogStrategy()
{
}
private:
std::string _logpath;
std::string _logfilename;
Mutex _mutex;
};
class Logger
{
public:
Logger()
{}
void UseConsoleStrategy()
{
_strategy = std::make_unique<ConsoleStrategy>();
}
void UseFileStrategy()
{
_strategy = std::make_unique<FileLogStrategy>();
}
void Debug(const std::string &message)
{
if (_strategy!= nullptr)
{
_strategy->LogRefresh("[DEBUG] " + GetCurrentDateTime() + " - " + message);
}
}
~Logger()
{}
private:
std::unique_ptr<LogStrategy> _strategy;
};
//日志对象全局使用
Logger logger;
#define ENABLE_LOG_CONSOLE() logger.UseConsoleStrategy()
#define ENABLE_LOG_FILE() logger.UseFileStrategy()
}
锁头文件
cpp
#pragma once
#include <iostream>
#include <pthread.h>
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_lock, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_lock);
}
pthread_mutex_t *Ptr()
{
return &_lock;
}
void Unlock()
{
pthread_mutex_unlock(&_lock);
}
~Mutex()
{
pthread_mutex_destroy(&_lock);
}
private:
pthread_mutex_t _lock;
};
class LockGuard // RAII风格代码
{
public:
LockGuard(Mutex &lock):_lockref(lock)
{
_lockref.Lock();
}
~LockGuard()
{
_lockref.Unlock();
}
private:
Mutex &_lockref;
};
测试
cpp
#include"Log.hpp"
using namespace Logger;
int main()
{
// ENABLE_LOG_CONSOLE();
// logger.Debug("开启控制台策略");
ENABLE_LOG_FILE();
logger.Debug("开启文件策略");
// std::cout<<GetCurrentDateTime()<<std::endl;
return 0;
}
结果为:


日志构建
cpp
#pragma once
#include "Mutex.hpp"
#include <iostream>
#include <cstdio>
#include <string>
#include <memory>
#include <sstream>
#include <chrono>
#include <iomanip>
#include <ctime>
#include <unistd.h>
#include <filesystem> // C++17
#include <fstream>
namespace Logger
{
enum class LogLevel
{
INFO, ///< 信息性消息,记录应用程序的正常运行状态(如启动、配置加载等)
WARNING, ///< 警告,表示潜在的问题或非预期的情形,但应用程序仍能继续运行
ERROR, ///< 错误,表示发生了严重的操作失败,但不影响整个应用程序的继续运行
FATAL, ///< 致命错误,表示严重的故障,通常会导致应用程序终止
DEBUG ///< 调试信息,用于开发和排错阶段,记录详细的内部状态或流程
};
std::string LogLevelToString(LogLevel level)
{
switch (level)
{
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
case LogLevel::DEBUG:
return "DEBUG";
default:
return "UNKNOWN";
}
}
std::string GetCurrentDateTime()
{
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::tm tm_info; // 用户提供的缓冲区
localtime_r(&now_c, &tm_info); // POSIX 线程安全函数
std::ostringstream oss;
oss << std::put_time(&tm_info, "%Y-%m-%d %H:%M:%S");
return oss.str();
}
// 输出角度 -- 刷新策略
// 1. 显示器打印
// 2. 文件写入
// 日志的生成:
// 1. 构建日志字符串
// 2. 根据不同的策略,进行刷新
//策略模式接口
class LogStrategy
{
public:
virtual void LogRefresh(const std::string &message) = 0;
virtual ~LogStrategy() = default;
};
// 控制台日志刷新策略, 日志将来要向显示器打印
class ConsoleStrategy : public LogStrategy
{
public:
// 显示器打印策略刷新
void LogRefresh(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cerr << message << std::endl; // ??
}
~ConsoleStrategy()
{
}
private:
Mutex _mutex;
};
const std::string defaultpath = "./log";
const std::string defaultfilename = "log.txt";
namespace fs = std::filesystem;
// 文件策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &name = defaultfilename)
: _logpath(path),
_logfilename(name)
{
if (std::filesystem::exists(_logpath))
return;
try
{
std::filesystem::create_directories(_logpath);
}
catch(const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\n';
}
if(!_logpath.empty()&&_logpath.back() != '/')
_logpath += '/';
}
// 文件策略刷新
void LogRefresh(const std::string &message) override
{
{
std::string targetlogfile = _logpath + _logfilename;
LockGuard lockguard(_mutex);
std::ofstream logFile(targetlogfile, std::ios::app);// 以追加模式打开文件
if (!logFile.is_open())
{
std::cerr << "无法打开日志文件: " << targetlogfile << std::endl;
return;
}
logFile << message << "\n";
logFile.close();
}
}
~FileLogStrategy()
{
}
private:
std::string _logpath;
std::string _logfilename;
Mutex _mutex;
};
class Logger
{
public:
Logger()
{}
void UseConsoleStrategy()
{
_strategy = std::make_unique<ConsoleStrategy>();
}
void UseFileStrategy()
{
_strategy = std::make_unique<FileLogStrategy>();
}
void Debug(const std::string &message)
{
if (_strategy!= nullptr)
{
_strategy->LogRefresh("[DEBUG] " + GetCurrentDateTime() + " - " + message);
}
}
//日志内容
//一条完整的日志信息=> [日志级别] + 当前时间 + 进程ID + 文件名 + 行号 + 日志信息
//我们想以RAII形式刷新日志信息
class LogMessage
{
public:
LogMessage(LogLevel level, const std::string &filename, size_t line, Logger &logger)
: _level(level), _filename(filename), _line(line),_logger(logger)
{
_cur_time = GetCurrentDateTime();
_pid=getpid();
// 构建日志左半部分信息
std::stringstream oss;
oss <<"["<<_cur_time<<"]"
<<"["<<LogLevelToString(_level)<<"]"
<<"["<<_pid<<"]"
<<"[" << filename << ":"
<< line << "]";
_LogInfo = oss.str();
}
template<typename T>
LogMessage& operator<<(const T&info)
{
std::stringstream oss;
oss<< info;
_LogInfo += oss.str();
return *this;
}
~LogMessage()
{
if (_logger._strategy != nullptr)
{
_logger._strategy->LogRefresh(_LogInfo);
}
}
private:
std::string _cur_time;
LogLevel _level;
pid_t _pid;
std::string _filename;
size_t _line;
std::string _LogInfo;
Logger &_logger ;//方便进行后续策略方式刷新
};
//对LogMessage进行()重载
//必须用拷贝,否则会导致<<重载的时候内容消失
LogMessage operator()(LogLevel level, const std::string filename, size_t line)
{
return LogMessage(level, filename, line, *this);
}
~Logger()
{}
private:
std::unique_ptr<LogStrategy> _strategy;
};
//日志对象全局使用
Logger logger;
#define ENABLE_LOG_CONSOLE() logger.UseConsoleStrategy()
#define ENABLE_LOG_FILE() logger.UseFileStrategy()
#define Log(level) logger(level, __FILE__, __LINE__)
}
测试
cpp
#include"Log.hpp"
using namespace Logger;
int main()
{
//日志构建测试
ENABLE_LOG_CONSOLE();
logger(LogLevel::DEBUG ,__FILE__, __LINE__)<<"这是一个日志测试,日志级别为DEBUG";
Log(LogLevel::ERROR)<<"这是一个日志测试,日志级别为ERROR";
Log(LogLevel::FATAL)<<"这是一个日志测试,日志级别为FATAL";
Log(LogLevel::INFO)<<"这是一个日志测试,日志级别为INFO";
Log(LogLevel::WARNING)<<"这是一个日志测试,日志级别为WARNING";
return 0;
}
结果为:

最终源码
Mutex.hpp
cpp
#pragma once
#include <iostream>
#include <pthread.h>
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_lock, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_lock);
}
pthread_mutex_t *Ptr()
{
return &_lock;
}
void Unlock()
{
pthread_mutex_unlock(&_lock);
}
~Mutex()
{
pthread_mutex_destroy(&_lock);
}
private:
pthread_mutex_t _lock;
};
class LockGuard // RAII风格代码
{
public:
LockGuard(Mutex &lock):_lockref(lock)
{
_lockref.Lock();
}
~LockGuard()
{
_lockref.Unlock();
}
private:
Mutex &_lockref;
};
Log.hpp
cpp
#pragma once
#include "Mutex.hpp"
#include <iostream>
#include <cstdio>
#include <string>
#include <memory>
#include <sstream>
#include <chrono>
#include <iomanip>
#include <ctime>
#include <unistd.h>
#include <filesystem> // C++17
#include <fstream>
namespace Logger
{
enum class LogLevel
{
INFO, ///< 信息性消息,记录应用程序的正常运行状态(如启动、配置加载等)
WARNING, ///< 警告,表示潜在的问题或非预期的情形,但应用程序仍能继续运行
ERROR, ///< 错误,表示发生了严重的操作失败,但不影响整个应用程序的继续运行
FATAL, ///< 致命错误,表示严重的故障,通常会导致应用程序终止
DEBUG ///< 调试信息,用于开发和排错阶段,记录详细的内部状态或流程
};
std::string LogLevelToString(LogLevel level)
{
switch (level)
{
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
case LogLevel::DEBUG:
return "DEBUG";
default:
return "UNKNOWN";
}
}
std::string GetCurrentDateTime()
{
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::tm tm_info; // 用户提供的缓冲区
localtime_r(&now_c, &tm_info); // POSIX 线程安全函数
std::ostringstream oss;
oss << std::put_time(&tm_info, "%Y-%m-%d %H:%M:%S");
return oss.str();
}
// 输出角度 -- 刷新策略
// 1. 显示器打印
// 2. 文件写入
// 日志的生成:
// 1. 构建日志字符串
// 2. 根据不同的策略,进行刷新
//策略模式接口
class LogStrategy
{
public:
virtual void LogRefresh(const std::string &message) = 0;
virtual ~LogStrategy() = default;
};
// 控制台日志刷新策略, 日志将来要向显示器打印
class ConsoleStrategy : public LogStrategy
{
public:
// 显示器打印策略刷新
void LogRefresh(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cerr << message << std::endl; // ??
}
~ConsoleStrategy()
{
}
private:
Mutex _mutex;
};
const std::string defaultpath = "./log";
const std::string defaultfilename = "log.txt";
namespace fs = std::filesystem;
// 文件策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &name = defaultfilename)
: _logpath(path),
_logfilename(name)
{
if (std::filesystem::exists(_logpath))
return;
try
{
std::filesystem::create_directories(_logpath);
}
catch(const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\n';
}
if(!_logpath.empty()&&_logpath.back() != '/')
_logpath += '/';
}
// 文件策略刷新
void LogRefresh(const std::string &message) override
{
{
std::string targetlogfile = _logpath + _logfilename;
LockGuard lockguard(_mutex);
std::ofstream logFile(targetlogfile, std::ios::app);// 以追加模式打开文件
if (!logFile.is_open())
{
std::cerr << "无法打开日志文件: " << targetlogfile << std::endl;
return;
}
logFile << message << "\n";
logFile.close();
}
}
~FileLogStrategy()
{
}
private:
std::string _logpath;
std::string _logfilename;
Mutex _mutex;
};
// 根据日志等级分类保存的策略类
class LevelFileLogStrategy : public LogStrategy {
public:
/**
* @brief 构造函数,指定日志根目录
* @param log_dir 存放等级日志文件的目录,默认为 "./logs"
*/
LevelFileLogStrategy(const std::string &log_dir = "./logs") : _log_dir(log_dir) {
// 确保目录存在
if (!std::filesystem::exists(_log_dir)) {
std::filesystem::create_directories(_log_dir);
}
// 规范化目录路径,末尾添加 '/'
if (!_log_dir.empty() && _log_dir.back() != '/') {
_log_dir += '/';
}
}
/**
* @brief 刷新日志:根据等级写入对应文件
* @param message 完整的日志消息(格式由 Logger::LogMessage 生成)
*/
void LogRefresh(const std::string &message) override {
// 1. 从消息中提取日志等级
LogLevel level = extractLogLevel(message);
// 2. 构造对应的文件名(例如:INFO.log)
std::string filename = _log_dir + LogLevelToString(level) + ".log";
// 3. 线程安全地追加写入文件
LockGuard lockguard(_mutex);
std::ofstream logFile(filename, std::ios::app);
if (!logFile.is_open()) {
std::cerr << "无法打开日志文件: " << filename << std::endl;
return;
}
logFile << message << "\n";
logFile.close();
}
private:
std::string _log_dir; // 日志根目录
Mutex _mutex; // 文件写入互斥锁
/**
* @brief 从日志消息中解析出等级
* @param msg 完整日志消息,格式为 "[时间][等级][PID][文件:行号] 用户内容"
* @return 对应的 LogLevel 枚举值,解析失败时默认返回 INFO
*/
LogLevel extractLogLevel(const std::string &msg) {
// 寻找第一个 ']' 的位置
size_t first_close = msg.find(']');
if (first_close == std::string::npos) {
return LogLevel::INFO; // 格式错误,默认 INFO
}
// 寻找第二个 '[' 的位置
size_t second_open = msg.find('[', first_close);
if (second_open == std::string::npos) {
return LogLevel::INFO;
}
// 寻找第二个 ']' 的位置
size_t second_close = msg.find(']', second_open);
if (second_close == std::string::npos) {
return LogLevel::INFO;
}
// 提取等级字符串(如 "INFO")
std::string level_str = msg.substr(second_open + 1, second_close - second_open - 1);
// 映射到 LogLevel 枚举
if (level_str == "INFO") return LogLevel::INFO;
if (level_str == "WARNING") return LogLevel::WARNING;
if (level_str == "ERROR") return LogLevel::ERROR;
if (level_str == "FATAL") return LogLevel::FATAL;
if (level_str == "DEBUG") return LogLevel::DEBUG;
return LogLevel::INFO; // 未知等级,默认 INFO
}
};
class Logger
{
public:
Logger()
{}
void UseConsoleStrategy()
{
_strategy = std::make_unique<ConsoleStrategy>();
}
void UseFileStrategy()
{
_strategy = std::make_unique<FileLogStrategy>();
}
void UseLevelFileStrategy(const std::string &log_dir = "./logs")
{
_strategy = std::make_unique<LevelFileLogStrategy>(log_dir);
}
void Debug(const std::string &message)
{
if (_strategy!= nullptr)
{
_strategy->LogRefresh("[DEBUG] " + GetCurrentDateTime() + " - " + message);
}
}
//日志内容
//一条完整的日志信息=> [日志级别] + 当前时间 + 进程ID + 文件名 + 行号 + 日志信息
//我们想以RAII形式刷新日志信息
class LogMessage
{
public:
LogMessage(LogLevel level, const std::string &filename, size_t line, Logger &logger)
: _level(level), _filename(filename), _line(line),_logger(logger)
{
_cur_time = GetCurrentDateTime();
_pid=getpid();
// 构建日志左半部分信息
std::stringstream oss;
oss <<"["<<_cur_time<<"]"
<<"["<<LogLevelToString(_level)<<"]"
<<"["<<_pid<<"]"
<<"[" << filename << ":"
<< line << "]";
_LogInfo = oss.str();
}
template<typename T>
LogMessage& operator<<(const T&info)
{
std::stringstream oss;
oss<< info;
_LogInfo += oss.str();
return *this;
}
~LogMessage()
{
if (_logger._strategy != nullptr)
{
_logger._strategy->LogRefresh(_LogInfo);
}
}
private:
std::string _cur_time;
LogLevel _level;
pid_t _pid;
std::string _filename;
size_t _line;
std::string _LogInfo;
Logger &_logger ;//方便进行后续策略方式刷新
};
//对LogMessage进行()重载
//必须用拷贝,否则会导致<<重载的时候内容消失
LogMessage operator()(LogLevel level, const std::string filename, size_t line)
{
return LogMessage(level, filename, line, *this);
}
~Logger()
{}
private:
std::unique_ptr<LogStrategy> _strategy;
};
//日志对象全局使用
Logger logger;
#define ENABLE_LOG_CONSOLE() logger.UseConsoleStrategy()
#define ENABLE_LOG_FILE() logger.UseFileStrategy()
#define Log(level) logger(level, __FILE__, __LINE__)
#define ENABLE_LOG_LEVEL_FILE(log_dir) logger.UseLevelFileStrategy(log_dir)
}
测试代码
cpp
#include"Log.hpp"
using namespace Logger;
int main()
{
//时间戳测试
// std::cout<<GetCurrentDateTime()<<std::endl;
//日志刷新测试
// ENABLE_LOG_CONSOLE();
// logger.Debug("开启控制台策略");
// ENABLE_LOG_FILE();
// logger.Debug("开启文件策略");
//日志构建测试
ENABLE_LOG_CONSOLE();
logger(LogLevel::DEBUG ,__FILE__, __LINE__)<<"这是一个日志测试,日志级别为DEBUG";
Log(LogLevel::ERROR)<<"这是一个日志测试,日志级别为ERROR";
Log(LogLevel::FATAL)<<"这是一个日志测试,日志级别为FATAL";
Log(LogLevel::INFO)<<"这是一个日志测试,日志级别为INFO";
Log(LogLevel::WARNING)<<"这是一个日志测试,日志级别为WARNING";
//日志分级文件测试
ENABLE_LOG_LEVEL_FILE("./logs");
Log(LogLevel::DEBUG)<<"这是一个日志测试,日志级别为DEBUG";
Log(LogLevel::ERROR)<<"这是一个日志测试,日志级别为ERROR";
Log(LogLevel::FATAL)<<"这是一个日志测试,日志级别为FATAL";
Log(LogLevel::INFO)<<"这是一个日志测试,日志级别为INFO";
Log(LogLevel::WARNING)<<"这是一个日志测试,日志级别为WARNING";
return 0;
}
线程池
设计

源码
日志文件代码就参考上文的Log.hpp
ThreadPool.hpp
cpp
#pragma once
#include<iostream>
#include<memory>
#include"Log.hpp"
#include"Thread.hpp"
#include"Mutex.hpp"
#include"Cond.hpp"
#include<vector>
#include<queue>
namespace ThreadPool
{
using namespace Logger;
const int default_thread_num = 5;
//测试用函数,用处不大
void test()
{
while(1)
{
Log(LogLevel::DEBUG)<<"我是一个新线程,我再运行ing....";
sleep(1);
}
}
template<typename T>
class threadpool
{
private:
void HandlerTask()
{
//测试
// char name[128];
// pthread_getname_np(pthread_self(), name, sizeof(name));
// while (true)
// {
// Log(LogLevel::DEBUG) << "正在运行:" << name;
// sleep(1);
// }
char name[128];
pthread_getname_np(pthread_self(), name, sizeof(name));
while (true)
{
T task;
{
// 保护临界区
LockGuard lockguard(_mutex);
// 检测任务。不休眠:1. 队列不为空 2. 线程池退出 -> 队列为空 && 线程池不退出
while (_tasks.empty() && _isRunning)
{
// 没有任务, 休眠
_Slaves_Sleep_Count++;
_cond.Wait(_mutex);
_Slaves_Sleep_Count--;
}
// 线程池退出了-> while 就要break -> 不能
// 1. 线程池退出 && _tasks empty
if (!_isRunning && _tasks.empty())
{
_mutex.Unlock();
break;
}
// 有任务, 取任务,本质:把任务由公共变成私有
// T -> task*
task = _tasks.front();
_tasks.pop();
}
// 处理任务, 约定
// 处理任务需要再临界区内部处理吗?不需要
Log(LogLevel::INFO) << name << "处理任务:";
task();
Log(LogLevel::DEBUG) << task.Result();
}
// 线程退出
Log(LogLevel::INFO) << name << " quit...";
}
public:
threadpool(int slave_num = default_thread_num)
: _isRunning(false), _Slavernum(slave_num), _Slaves_Sleep_Count(0)
{
_Slaves.reserve(_Slavernum);
//version1
// for(size_t i=0; i<_Slavernum; ++i)
// {
// _Slaves.emplace_back([this](){
// this->HandlerTask();
// });
// }
//version2
auto fun =std::bind(&threadpool::HandlerTask, this);
for(size_t i=0; i<_Slavernum; ++i)
{
_Slaves.emplace_back(fun);
}
}
~threadpool()
{
}
void Init()
{
if(_isRunning)
{
Log(LogLevel::WARNING) << "线程池已经运行";
return;
}
_isRunning = true;
for(auto &slave:_Slaves)
{
slave.Start();
}
}
void Stop()
{
//version1
if(!_isRunning)
{
Log(LogLevel::WARNING) << "线程池未运行";
return;
}
_isRunning = false;
for(auto &slave:_Slaves)
{
slave.Die();
}
// version 2
// 1. _isrunning = false
// 2. 处理完成tasks所有的任务
// 线程状态: 休眠,正在处理任务 -> 让所有线程全部唤醒
// HandlerTask自动break
_mutex.Lock();
_isRunning = false;
if (_Slaves_Sleep_Count > 0)
_cond.Broadcast();
_mutex.Unlock();
}
void Wait()
{
for(auto &slave:_Slaves)
{
slave.Join();
}
}
void Enqueue(T in)
{
// 生产者线程调用这个函数来添加任务
// 1. 加锁
// 2. 将任务添加到队列中
// 3. 解锁
// 4. 通知一个等待的线程有新任务了
_mutex.Lock();
_tasks.push(in);
if (_Slaves_Sleep_Count > 0)
_cond.Signal();
_mutex.Unlock();
}
private:
bool _isRunning;
size_t _Slavernum;
std::vector<Thread> _Slaves;
std::queue<T> _tasks;// 临界资源
LockModule::Mutex _mutex;
CondModule::Cond _cond;
size_t _Slaves_Sleep_Count; // 休眠的线程数量
};
} // namespace ThreadPool
Cond.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include "Mutex.hpp"
namespace CondModule
{
class Cond
{
public:
Cond()
{
pthread_cond_init(&_cond, nullptr);
}
void Wait(LockModule::Mutex &mutex)
{
int n = pthread_cond_wait(&_cond, mutex.GetMutexOriginal());
(void)n;
}
void Signal()
{
int n = pthread_cond_signal(&_cond);
(void)n;
}
void Broadcast()
{
int n = pthread_cond_broadcast(&_cond);
(void)n;
}
~Cond()
{
pthread_cond_destroy(&_cond);
}
private:
pthread_cond_t _cond;
};
}
Thread.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include "Log.hpp"
namespace ThreadPool
{
using namespace Logger;
static int gnumber = 1;
using callback_t = std::function<void()>;
enum class TSTATUS
{
THREAD_NEW,
THREAD_RUNNING,
THREAD_STOP
};
std::string Status2String(TSTATUS s)
{
switch (s)
{
case TSTATUS::THREAD_NEW:
return "THREAD_NEW";
case TSTATUS::THREAD_RUNNING:
return "THREAD_RUNNING";
case TSTATUS::THREAD_STOP:
return "THREAD_STOP";
default:
return "UNKNOWN";
}
}
std::string IsJoined(bool joinable)
{
return joinable ? "true" : "false";
}
class Thread
{
private:
void ToRunning()
{
_status = TSTATUS::THREAD_RUNNING;
}
void ToStop()
{
_status = TSTATUS::THREAD_STOP;
}
static void *ThreadRoutine(void *args)
{
Thread *self = static_cast<Thread *>(args);
pthread_setname_np(self->_tid, self->_name.c_str());
self->_cb();
self->ToStop();
return nullptr;
}
public:
Thread(callback_t cb)
: _tid(-1), _status(TSTATUS::THREAD_NEW), _joinable(true), _cb(cb), _result(nullptr)
{
_name = "New-Thread-" + std::to_string(gnumber++);
}
bool Start()
{
int n = pthread_create(&_tid, nullptr, ThreadRoutine, this);
if (n != 0)
return false;
ToRunning();
return true;
}
void Join()
{
if (_joinable)
{
int n = pthread_join(_tid, &_result);
if (n != 0)
{
std::cerr << "join error: " << n << std::endl;
return;
}
(void)_result;
_status = TSTATUS::THREAD_STOP;
Log(LogLevel::DEBUG) << _name << " join success";
}
else
{
std::cerr << "error, thread join status: " << IsJoined(_joinable) << std::endl;
}
}
// 暂停
// void Stop() // restart()
// {
// // 让线程暂停
// }
void Die()
{
if (_status == TSTATUS::THREAD_RUNNING)
{
pthread_cancel(_tid);
_status = TSTATUS::THREAD_STOP;
}
}
void Detach()
{
if (_status == TSTATUS::THREAD_RUNNING && _joinable)
{
pthread_detach(_tid);
_joinable = false;
}
else
{
std::cerr << "detach " << _name << " failed" << std::endl;
}
}
void PrintInfo()
{
std::cout << "thread name : " << _name << std::endl;
std::cout << "thread _tid : " << _tid << std::endl;
std::cout << "thread _status : " << Status2String(_status) << std::endl;
std::cout << "thread _joinable : " << IsJoined(_joinable) << std::endl;
}
~Thread()
{
}
private:
std::string _name;
pthread_t _tid;
TSTATUS _status;
bool _joinable;
// 线程要有自己的任务处理,即回调函数
callback_t _cb;
// 线程退出信息
void *_result;
};
}
Mutex.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include"Log.hpp"
namespace LockModule
{
// 对锁进行封装,可以独立使用
class Mutex
{
public:
// 删除不需要的拷贝和赋值
Mutex(const Mutex &) = delete;
const Mutex &operator=(const Mutex &) = delete;
// 构造函数
Mutex()
{
int n = pthread_mutex_init(&_mutex, nullptr);
(void)n;
}
// 加锁
void Lock()
{
int n = pthread_mutex_lock(&_mutex);
(void)n;
}
// 解锁
void Unlock()
{
int n = pthread_mutex_unlock(&_mutex);
(void)n;
}
// 获取原始指针
pthread_mutex_t *GetMutexOriginal()
{
return &_mutex;
}
// 析构函数
~Mutex()
{
int n = pthread_mutex_destroy(&_mutex);
(void)n;
}
private:
pthread_mutex_t _mutex;
};
// 采用RAII风格,进行锁管理
class LockGuard
{
public:
// 构造函数,创建时自动加锁
LockGuard(Mutex &mutex) : _mutex(mutex)
{
_mutex.Lock();
}
// 析构函数,自动解锁
~LockGuard()
{
_mutex.Unlock();
}
private:
Mutex &_mutex; // 引用被管理的互斥锁
};
}
Task.hpp
cpp
#include<string>
class Task
{
public:
Task(){}
Task(int x, int y):_x(x), _y(y)
{}
void operator()()
{
_result = _x + _y;
}
std::string Result()
{
return std::to_string(_x) + "+" + std::to_string(_y) + "=" + std::to_string(_result);
}
~Task(){}
private:
int _x;
int _y;
int _result;
};
测试:
cpp
#include"ThreadPool.hpp"
#include"Task.hpp"
using namespace ThreadPool;
int main()
{
ENABLE_LOG_CONSOLE();
srand((long)time(nullptr) ^ getpid());
// auto pool=std::make_unique<threadpool<int>>(10);
auto pool=std::make_unique<threadpool<Task>>(10);
pool->Init();
sleep(3);
int cnt = 10;
while (cnt--)
{
int x = rand() % 10 + 1;
usleep(137);
int y = rand() % 20;
Task t(x, y);
pool->Enqueue(t);
sleep(1);
// tp->Enqueue([](){
// LOG(LogLevel::DEBUG) << "我是一个任务, 正在被处理...";
// }
// );
// sleep(1);
}
pool->Stop();
pool->Wait();
sleep(3);
return 0;
}
结果为:

用单例模式实现线程池
单例模式概念
单例模式 (Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例 ,并提供一个全局访问点来获取该实例。
单例模式特点
-
唯一性:类在程序生命周期内最多只有一个实例对象。
-
全局访问性 :提供一个静态方法(如
getInstance()),允许从任何地方访问该实例。 -
构造私有:将构造函数、拷贝构造函数、赋值操作符设为私有或删除,防止外部创建新对象。
-
生命周期管理:实例的创建时机和销毁方式需要精心设计(如饿汉式在程序启动时创建,懒汉式在首次访问时创建)。
-
线程安全性:在多线程环境下,必须确保实例化过程的线程安全,防止创建多个实例。
饿汉/懒汉
单例模式有两种经典实现方式:饿汉式 和 懒汉式。它们的区别在于实例创建的时机。
饿汉式(Eager Initialization)
特点:
-
实例在程序启动时(或类加载时)就被创建,无论是否被使用。
-
实现简单,天生线程安全 (因为实例在进入
main()之前已经构造完成,不存在竞争条件)。 -
如果单例对象占用资源较多且可能不会被使用,会造成资源浪费。
-
无法控制实例创建的精确时机(例如依赖某些运行时配置时可能不适用)。
代码框架
cpp
// SingletonEager.h
#pragma once
#include <iostream>
class SingletonEager {
public:
// 删除拷贝构造和赋值操作
SingletonEager(const SingletonEager&) = delete;
SingletonEager& operator=(const SingletonEager&) = delete;
// 全局访问点
static SingletonEager* getInstance() {
return &instance;
}
void doSomething() {
std::cout << "SingletonEager::doSomething()" << std::endl;
}
private:
// 私有构造函数和析构函数
SingletonEager() {
std::cout << "SingletonEager constructor" << std::endl;
}
~SingletonEager() = default;
// 静态成员变量,在程序启动时初始化
static SingletonEager instance;
};
// SingletonEager.cpp
#include "SingletonEager.h"
// 定义并初始化静态成员(在main之前完成)
SingletonEager SingletonEager::instance;
说明:
-
静态成员
instance在程序启动时(进入main前)被初始化。 -
由于静态初始化是线程安全的(C++11保证局部静态变量的初始化线程安全,但非局部静态变量在C++98/03中可能不是线程安全的?实际上非局部静态变量的初始化发生在
main之前,单线程环境,所以也是安全的),饿汉式无需额外加锁。
懒汉式(Lazy Initialization)
特点:
-
实例在首次调用
getInstance()时才创建,延迟加载。 -
节省资源,如果单例从未被使用,则不会创建。
-
需要处理线程安全,否则多线程环境下可能导致创建多个实例。
-
实现稍复杂,需要考虑加锁、双重检查锁(DCLP)或C++11后的局部静态变量方式。
传统版本
cpp
// SingletonLazy.h
#pragma once
#include <iostream>
#include <mutex>
class SingletonLazy {
public:
SingletonLazy(const SingletonLazy&) = delete;
SingletonLazy& operator=(const SingletonLazy&) = delete;
static SingletonLazy* getInstance() {
std::lock_guard<std::mutex> lock(mutex_);
if (instance_ == nullptr) {
instance_ = new SingletonLazy();
}
return instance_;
}
void doSomething() {
std::cout << "SingletonLazy::doSomething()" << std::endl;
}
private:
SingletonLazy() {
std::cout << "SingletonLazy constructor" << std::endl;
}
~SingletonLazy() = default;
static SingletonLazy* instance_;
static std::mutex mutex_;
};
// SingletonLazy.cpp
#include "SingletonLazy.h"
SingletonLazy* SingletonLazy::instance_ = nullptr;
std::mutex SingletonLazy::mutex_;
C++11版本(推荐!!!)
cpp
// SingletonLazyModern.h
#pragma once
#include <iostream>
class SingletonLazyModern {
public:
SingletonLazyModern(const SingletonLazyModern&) = delete;
SingletonLazyModern& operator=(const SingletonLazyModern&) = delete;
static SingletonLazyModern& getInstance() {
static SingletonLazyModern instance; // 线程安全的局部静态变量
return instance;
}
void doSomething() {
std::cout << "SingletonLazyModern::doSomething()" << std::endl;
}
private:
SingletonLazyModern() {
std::cout << "SingletonLazyModern constructor" << std::endl;
}
~SingletonLazyModern() = default;
};
代码
只修改了这个文件和测试文件,其他的没有修改
ThreadPool.hpp
cpp
#pragma once
#include <iostream>
#include <memory>
#include "Log.hpp"
#include "Thread.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
#include <vector>
#include <queue>
namespace ThreadPool
{
using namespace Logger;
const int default_thread_num = 5;
// 测试用函数
void test()
{
while(1)
{
Log(LogLevel::DEBUG) << "我是一个新线程,我再运行ing....";
sleep(1);
}
}
template<typename T>
class threadpool
{
private:
void HandlerTask()
{
char name[128];
pthread_getname_np(pthread_self(), name, sizeof(name));
while (true)
{
T task;
{
LockModule::LockGuard lockguard(_mutex); // 使用 RAII 管理锁,避免手动解锁
while (_tasks.empty() && _isRunning)
{
_Slaves_Sleep_Count++;
_cond.Wait(_mutex);
_Slaves_Sleep_Count--;
}
if (!_isRunning && _tasks.empty())
{
// 退出前解锁(LockGuard 析构时会自动解锁,无需手动)
break;
}
task = _tasks.front();
_tasks.pop();
}
Log(LogLevel::INFO) << name << "处理任务:";
task();
Log(LogLevel::DEBUG) << task.Result();
}
Log(LogLevel::INFO) << name << " quit...";
}
// 构造函数私有,禁止外部直接创建
threadpool(int slave_num = default_thread_num)
: _isRunning(false), _Slavernum(slave_num), _Slaves_Sleep_Count(0)
{
_Slaves.reserve(_Slavernum);
auto fun = std::bind(&threadpool::HandlerTask, this);
for (size_t i = 0; i < _Slavernum; ++i)
{
_Slaves.emplace_back(fun);
}
}
// 禁止拷贝和赋值(修正:类名应为 threadpool)
threadpool(const threadpool &) = delete;
threadpool &operator=(const threadpool &) = delete;
public:
static threadpool<T>* Instance()
{
if (_instance == nullptr)
{
LockModule::LockGuard lockguard(_lock);
if (_instance == nullptr)
{
// 使用 reset(new ...) 替代 std::make_unique,因为构造函数私有
_instance.reset(new threadpool<T>());
_instance->Init(); // 调用 Init 而非 Start
Log(LogLevel::INFO) << "第一次使用线程池,创建线程池对象";
}
}
return _instance.get();
}
~threadpool()
{
}
void Init()
{
if (_isRunning)
{
Log(LogLevel::WARNING) << "线程池已经运行";
return;
}
_isRunning = true;
for (auto &slave : _Slaves)
{
slave.Start();
}
}
void Stop()
{
if (!_isRunning)
{
Log(LogLevel::WARNING) << "线程池未运行";
return;
}
// 停止线程池
_mutex.Lock();
_isRunning = false;
if (_Slaves_Sleep_Count > 0)
_cond.Broadcast();
_mutex.Unlock();
// 等待所有线程退出(可选)
// Wait();
}
void Wait()
{
for (auto &slave : _Slaves)
{
slave.Join();
}
}
void Enqueue(T in)
{
_mutex.Lock();
_tasks.push(in);
if (_Slaves_Sleep_Count > 0)
_cond.Signal();
_mutex.Unlock();
}
private:
bool _isRunning;
size_t _Slavernum;
std::vector<Thread> _Slaves;
std::queue<T> _tasks; // 临界资源
LockModule::Mutex _mutex;
CondModule::Cond _cond;
size_t _Slaves_Sleep_Count; // 休眠的线程数量
// 单例模式静态成员
static std::unique_ptr<threadpool<T>> _instance;
static LockModule::Mutex _lock; // 类型改为 LockModule::Mutex
};
// 静态成员定义(必须放在命名空间内)
template<typename T>
std::unique_ptr<threadpool<T>> threadpool<T>::_instance = nullptr;
template<typename T>
LockModule::Mutex threadpool<T>::_lock;
} // namespace ThreadPool
Main.cpp
cpp
#include"ThreadPool.hpp"
#include"Task.hpp"
using namespace ThreadPool;
int main()
{
ENABLE_LOG_CONSOLE();
srand((long)time(nullptr) ^ getpid());
// auto pool=std::make_unique<threadpool<int>>(10);
auto pool = threadpool<Task>::Instance();
pool->Init();
sleep(3);
int cnt = 10;
while (cnt--)
{
int x = rand() % 10 + 1;
usleep(137);
int y = rand() % 20;
Task t(x, y);
threadpool<Task>::Instance()->Enqueue(t);
sleep(1);
// pool->Enqueue(t);
// sleep(1);
// tp->Enqueue([](){
// LOG(LogLevel::DEBUG) << "我是一个任务, 正在被处理...";
// }
// );
// sleep(1);
}
threadpool<Task>::Instance()->Stop();
threadpool<Task>::Instance()->Wait();
// pool->Stop();
// pool->Wait();
sleep(3);
return 0;
}
结果为:

本期内容就到这里了,喜欢的话请点个赞谢谢
封面图自取:
