【Linux线程】Linux系统多线程(八):<策略模式>日志系统的封装实现

🎬 个人主页艾莉丝努力练剑
专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬 艾莉丝的简介:


文章目录

  • [1 ~> 回顾线程互斥(Mutex)------ 抢票背后的"生存法则"](#1 ~> 回顾线程互斥(Mutex)—— 抢票背后的“生存法则”)
    • [1.1 为什么需要互斥?](#1.1 为什么需要互斥?)
    • [1.2 核心概念](#1.2 核心概念)
    • [1.3 互斥锁(Mutex)实战](#1.3 互斥锁(Mutex)实战)
      • [1.3.1 经典案例:抢票系统](#1.3.1 经典案例:抢票系统)
  • [2 ~> 生产消费模型(Producer-Consumer)](#2 ~> 生产消费模型(Producer-Consumer))
    • [2.1 为什么要用这个模型?](#2.1 为什么要用这个模型?)
    • [2.2 遵循 321 原则](#2.2 遵循 321 原则)
  • [3 ~> 工业级日志系统封装(Logging System)](#3 ~> 工业级日志系统封装(Logging System))
  • [4 ~> 日志总结](#4 ~> 日志总结)
  • 结尾

1 ~> 回顾线程互斥(Mutex)------ 抢票背后的"生存法则"

1.1 为什么需要互斥?

在多线程环境下,变量的共享是一把双刃剑。

1.1.1【小故事:疯狂的售票机】

想象一个火车站售票系统,剩余票数 tickets 是 100。有 5 个窗口(线程)同时开抢。

如果没有保护,线程 A 看到还有 1 张票,准备执行"减 1"操作,但此时它的时间片到了。线程 B 瞬间切入,也看到有 1 张票,执行了减 1 变成 0。等线程 A 回来,它并不知道票没了,继续减 1,结果 tickets 变成了 -1。这就是经典的数据不一致问题。

1.2 核心概念

临界资源 :多线程执行流共享的、被保护的资源(如:tickets)。

临界区:每个线程内部,访问临界资源的那段代码。

互斥:任何时刻,有且只有一个执行流进入临界区。

原子性:一个操作要么做了,要么没做,没有中间状态(不可被打断)。

1.3 互斥锁(Mutex)实战

1.3.1 经典案例:抢票系统

cpp 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int tickets = 100; // 临界资源
pthread_mutex_t lock; // 定义互斥锁

void* route(void* arg) 
{
    char* id = (char*)arg;
    while (1) 
    {
        pthread_mutex_lock(&lock); // 加锁:进入临界区
        if (tickets > 0) 
        {
            usleep(1000); // 模拟业务处理
            printf("%s sells ticket: %d\n", id, tickets);
            tickets--;
            pthread_mutex_unlock(&lock); // 解锁:离开临界区
        } else {
            pthread_mutex_unlock(&lock); // 记得死路也要解锁!
            break;
        }
    }
    return nullptr;
}

int main() 
{
    pthread_mutex_init(&lock, NULL); // 初始化锁
    pthread_t t1, t2;
    pthread_create(&t1, NULL, route, (void*)"Thread 1");
    pthread_create(&t2, NULL, route, (void*)"Thread 2");
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&lock); // 销毁锁
    return 0;
}

2 ~> 生产消费模型(Producer-Consumer)

2.1 为什么要用这个模型?

为了解耦平衡忙闲

生产者只管把数据丢进缓冲区,消费者只管从缓冲区取数据。缓冲区就像一个"蓄水池",缓解了双方速度不匹配的问题。

2.2 遵循 321 原则

3 种关系:

  • 生产者 vs 生产者(互斥)

  • 消费者 vs 消费者(互斥)

  • 生产者 vs 消费者(互斥与同步)

2 个角色:生产者、消费者。

1 个交易场所:特定的缓冲区。


3 ~> 工业级日志系统封装(Logging System)

在大型项目中,cout 是极其不专业的。我们需要一个能够记录时间、等级、文件、行号 并支持并发安全的日志系统。

日志有现成的解决方案,如:spdlog、glog、Boost.Log、Log4cxx等等,我们依l旧采用自定义日志的方式。

这里我们采用设计模式-策略模式来进行日志的设计。

我们想要的日志格式类型如下所示:

bash 复制代码
[可读性很好的时间] [⽇志等级] [进程pid] [打印对应⽇志的⽂件名][行号] - 消息内容,支持可变参数

[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [17] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [18] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [20] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [21] - hello world
[2024-08-04 12:27:03] [WARNING] [202938] [main.cc] [23] - hello world

3.1 日志等级定义

cpp 复制代码
#define INFO  0
#define DEBUG 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

3.2 核心封装:Logger 类

封装思路:我们使用 C++ 的流式风格(类似 LOG(INFO) << "msg")。

3.2.1【核心逻辑:利用临时对象的析构刷新日志】

这是一个非常优雅的设计:

  • LOG(level) 返回一个临时对象。

  • 通过 << 运算符重载拼接消息。

  • 关键点:当这一行代码结束时,临时对象析构,在析构函数里将完整的一行日志打印出来。

3.2.2 Logger类的封装代码

cpp 复制代码
#include <iostream>
#include <string>
#include <ctime>
#include <pthread.h>

// 日志消息类
class LogMessage 
{
public:
    LogMessage(int level, const char* file, int line)
        : _level(level), _file(file), _line(line) 
        {
        // 预填充:时间、等级、文件名、行号
        _buffer = "[" + GetTimestamp() + "][" + GetLevelString() + "][" 
                  + _file + ":" + std::to_string(_line) + "] ";
    }

    // 重载 << 运算符
    template <typename T>
    LogMessage& operator<<(const T& val) 
    {
        _buffer += std::to_string(val); // 实际实现中建议用 ostringstream
        return *this;
    }

    // 析构时刷新
    ~LogMessage() 
    {
        pthread_mutex_lock(&_log_mutex);
        std::cout << _buffer << std::endl;
        pthread_mutex_unlock(&_log_mutex);
    }

private:
    std::string GetTimestamp() 
    {
        time_t now = time(0);
        char buf[32];
        strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&now));
        return buf;
    }

    std::string GetLevelString() 
    {
        switch(_level) 
        {
            case INFO: return "INFO";
            case DEBUG: return "DEBUG";
            // ... 省略其他
            default: return "UNKNOW";
        }
    }

    int _level;
    std::string _file;
    int _line;
    std::string _buffer;
    static pthread_mutex_t _log_mutex; // 保证多线程打印不串行
};

pthread_mutex_t LogMessage::_log_mutex = PTHREAD_MUTEX_INITIALIZER;

// 宏定义:简化调用,自动注入文件名和行号
#define LOG(level) LogMessage(level, __FILE__, __LINE__)

3.3 使用示例

cpp 复制代码
void* task(void* arg) 
{
    LOG(INFO) << "Thread is running, task_id: " << 1024;
    LOG(WARNING) << "CPU usage is high: " << 95 << "%";
    return nullptr;
}

3.4 注意事项

C++11也已经封装了锁比如:

cpp 复制代码
std::lock_guard<std:mutex> lock(mutex);

这里我们直接用我们自己封装的锁(Mutex.hpp)


4 ~> 日志总结

  • 互斥锁 是并发控制的基础,必须保证 "加锁-业务-解锁" 的闭环。

  • 生产消费模型是处理高并发数据的标准范式,核心在于缓冲区的管理。

  • 日志系统封装 体现了面向对象设计的思想,利用 C++ RAII机制(析构函数自动处理)可以极大简化代码调用。

  • 预定义宏 __FILE____LINE__ 是定位 Bug 的利器,务必集成到日志中。


结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 艾莉丝努力练剑 C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日练剑达成!" "技术之路难免有困惑,但同行的人会让前进更有方向。" |

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!

往期回顾

【Linux线程】Linux系统多线程(七):<线程同步与互斥>线程同步(下)

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

相关推荐
HalvmånEver2 小时前
MySQL数据库操作
linux·数据库·学习·mysql
深圳元器猫2 小时前
AI服务器功率电感解决方案_4.7μH电感应对GPU高负载挑战
服务器·dc-dc·新能源汽车·元器猫·功率电感
2301_780789662 小时前
游戏盾是如何防护各个重要的游戏端口呢
服务器·网络·人工智能·游戏·架构·零信任
小夏子_riotous2 小时前
Docker学习路径——4、制作/更改镜像
学习·docker
盐焗鹌鹑蛋2 小时前
【C++】string模拟实现
c++
何中应2 小时前
清理服务器磁盘空间的方法
linux·运维·服务器
特种加菲猫2 小时前
C++进阶:模板深度解析与继承机制初探
开发语言·c++
旖-旎2 小时前
递归(快速幂)(5)
c++·算法·力扣·递归
RisunJan2 小时前
Linux命令-nfsstat(列出NFS客户端和服务器的工作状态)
linux·服务器