1. 项目概述
在Linux多线程编程中,日志输出是一个看似简单却暗藏玄机的问题。当多个线程同时向标准输出写入日志时,经常会出现输出内容错乱、断行异常等问题。这是因为在Linux系统中,标准输出(stdout)本质上也是一个文件描述符,多个线程同时写入这个共享资源时,如果没有适当的同步机制,就会导致数据竞争。
我最近在开发一个多线程服务时,就遇到了这样的问题:日志信息经常混杂在一起,难以阅读和调试。为了解决这个问题,我设计了一个基于策略模式的线程安全日志模块。这个模块不仅解决了多线程日志输出的同步问题,还提供了灵活的日志输出策略,可以方便地切换输出目标(如控制台或文件)。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心设计思路
2.1 策略模式的应用
策略模式是一种行为设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。在我们的日志模块中,不同的日志输出方式(如输出到控制台或文件)就是不同的策略。
这种设计有以下几个优点:
- 灵活性:可以轻松添加新的日志输出策略而不影响现有代码
- 可维护性:每种策略的实现都封装在单独的类中,便于维护
- 可测试性:可以单独测试每种策略的实现
2.2 线程安全考虑
在多线程环境下,任何共享资源都需要适当的同步机制。在我们的日志模块中,有两个关键点需要考虑线程安全:
- 控制台输出:多个线程同时调用std::cout会导致输出混乱
- 文件输出:多个线程同时打开和写入同一个文件会导致数据损坏
我们使用互斥锁(mutex)来保护这些共享资源的访问。具体来说,每个策略类内部都维护一个互斥锁,确保同一时间只有一个线程可以执行输出操作。
3. 日志模块实现细节
3.1 基础架构设计
首先,我们定义一个抽象的日志策略基类:
cpp复制class LogStrategy {
public:
virtual void SyncStrategy(const std::string &s) = 0;
virtual ~LogStrategy() = default;
};
这个基类只有一个纯虚函数SyncStrategy,所有具体的日志策略都需要实现这个方法。
3.2 控制台输出策略
控制台输出策略的实现相对简单,但需要注意线程安全:
cpp复制class WindowSyncStrategy : public LogStrategy {
public:
WindowSyncStrategy() {}
~WindowSyncStrategy() = default;
void SyncStrategy(const std::string &s) override {
Mutex_Moudle::Mutex_Grard guard(lock);
std::cout << s << std::endl;
}
private:
Mutex_Moudle::Mutex lock;
};
关键点:
- 使用互斥锁保护std::cout的访问
- 使用RAII风格的锁守卫(Mutex_Grard)确保锁的自动释放
- 每条日志都单独刷新(std::endl包含刷新操作)
3.3 文件输出策略
文件输出策略更为复杂,需要考虑文件路径处理、目录创建等问题:
cpp复制class FileSyncStrategy : public LogStrategy {
public:
FileSyncStrategy(const std::string name = "log.txt",
const std::string path = "./log")
: _name(name), _path
