C++项目:日志&&线程池

前面我们已经大致学习过线程相关的内容。线程的申请、控制、阻塞、销毁和内存一样,频繁地申请调用资源会导致大量的性能损失,而利用我们前面学过的"池化"技术就可以一定程度上缓解这个问题。

本期我们就来深入学习如何设计线程池,写出基本版本的线程池,并学习用单例模式优化线程池,并给出相关的代码。

代码已经上传至作者的个人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. 创建型模式:关注对象的创建过程,将对象的创建与使用分离,提升灵活性和复用性。

  2. 结构型模式:关注类和对象的组合,通过继承或组合构建更大的结构。

  3. 行为型模式:关注对象之间的职责分配和通信,描述对象如何协作完成单个对象无法完成的任务。

常见的设计模式介绍

创建型模式

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-420x7f8c4bfff700
源文件位置 文件名和行号,方便定位代码位置 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;
}

结果为:

本期内容就到这里了,喜欢的话请点个赞谢谢

封面图自取:

相关推荐
暴力求解1 小时前
Linux---进程(五)进程调度
linux·运维·服务器
wsad05321 小时前
Linux 用户和组管理完整指南(中英文参数对照)
linux·运维·服务器
weixin_421585011 小时前
表示学习发展历程
学习
tankeven2 小时前
HJ93 数组分组
c++·算法
S-码农2 小时前
Linux进程通讯——共享内存
linux
EmbedLinX2 小时前
嵌入式Linux之U-Boot
linux·服务器·笔记·学习
程序设计实验室2 小时前
从挖矿木马入侵到 Docker Rootless 加固,我的服务器安全复盘
linux·docker
雷电法拉珑2 小时前
财务数据批量采集
linux·前端·python
汉克老师3 小时前
GESP2024年3月认证C++二级( 第一部分选择题(9-15))
c++·循环结构·分支结构·gesp二级·gesp2级