软件帮帮网
柔彩主题三 · 更轻盈的阅读体验

线程安全的日志打印:多线程环境下如何避免日志混乱

发布时间:2026-01-17 12:50:55 阅读:249 次

在开发高并发系统时,日志是排查问题的重要工具。但你有没有遇到过这种情况:多个线程同时写日志,结果日志内容混在一起,一行还没写完,另一行的内容就插了进来?这种日志错乱让调试变得异常困难。

为什么普通日志打印不安全

假设你用一个简单的文件写入方式记录日志:

void log(const char* msg) {
    FILE* f = fopen("app.log", "a");
    fprintf(f, "[%ld] %s\n", time(NULL), msg);
    fclose(f);
}

这段代码在单线程下没问题,但在多线程环境中,两个线程可能同时打开同一个文件,写入位置重叠,导致日志交错甚至丢失。

加锁是最直接的解决办法

给日志函数加上互斥锁,就能保证同一时间只有一个线程能写日志:

#include <pthread.h>

pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;

void safe_log(const char* msg) {
    pthread_mutex_lock(&log_mutex);
    
    FILE* f = fopen("app.log", "a");
    fprintf(f, "[%ld] %s\n", time(NULL), msg);
    fclose(f);
    
    pthread_mutex_unlock(&log_mutex);
}

这样一来,即使十个线程同时调用 safe_log,也会排队执行,日志顺序清晰可读。

异步日志提升性能

虽然加锁解决了安全问题,但频繁写磁盘会影响性能。更高效的做法是把日志写入一个线程安全的队列,由单独的日志线程负责写文件。

比如用一个循环缓冲区配合原子操作或互斥锁保护,主线程只做快速入队,后台线程慢慢处理落盘。这样既保证了线程安全,又减少了对业务线程的阻塞。

使用成熟的日志库

自己实现容易出错,大多数项目会直接选用像 spdlog、glog 或 log4cpp 这类支持线程安全的日志库。它们默认做了锁保护,有些还支持异步模式,配置简单,稳定性高。

比如 spdlog 的基本用法:

#include <spdlog/spdlog.h>

int main() {
    auto logger = spdlog::basic_logger_mt("file_logger", "logs/basic.txt");
    logger->info("这是一个线程安全的日志消息");
    return 0;
}

其中 basic_logger_mtmt 就表示 multi-threaded,内部已做好同步处理。

实际场景中的坑

有位开发者在做域名解析服务时,给每个请求都打日志。系统上线后并发一上来,日志文件里出现了大量半截内容,像是“[1712345678] 正在解析www.xxx”后面突然接上了另一个域名。查了半天才发现是日志没做同步。后来改用 spdlog 异步模式,问题立刻消失,服务也更稳了。

线程安全的日志不是可有可无的优化,而是高并发服务的基础设施。别等到线上出问题才回头补课。