
🎬 个人主页 :艾莉丝努力练剑
❄专栏传送门 :《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))
-
- [3.1 日志等级定义](#3.1 日志等级定义)
- [3.2 核心封装:Logger 类](#3.2 核心封装:Logger 类)
-
- 3.2.1【核心逻辑:利用临时对象的析构刷新日志】
- [3.2.2 Logger类的封装代码](#3.2.2 Logger类的封装代码)
- [3.3 使用示例](#3.3 使用示例)
- [3.4 注意事项](#3.4 注意事项)
- [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系统多线程(七):<线程同步与互斥>线程同步(下)
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა
