导读:本期聚焦于小伙伴创作的《C++怎么利用C++11的thread实现异步非阻塞的文件日志系统》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C++怎么利用C++11的thread实现异步非阻塞的文件日志系统》有用,将其分享出去将是对创作者最好的鼓励。

在C++项目开发中,日志是排查问题、记录运行状态的重要工具,传统的同步写日志方式会在写入磁盘时阻塞当前线程,当日志量较大时严重影响程序性能。利用C++11提供的thread库可以很方便地实现异步非阻塞的文件日志系统,让日志写入操作在后台线程执行,主线程无需等待写入完成。

C++怎么利用C++11的thread实现异步非阻塞的文件日志系统

核心设计思路

异步非阻塞日志系统的核心是将日志生产者和消费者解耦,主线程作为生产者生成日志内容,后台工作线程作为消费者负责将日志写入文件,两者通过线程安全的队列通信。整体设计包含以下几个部分:

  • 线程安全的日志队列:用于存储主线程提交的日志内容,支持多线程并发写入和读取
  • 后台工作线程:由C++11的thread创建,循环从日志队列中取出日志并写入文件
  • 日志写入模块:负责打开、写入、关闭日志文件,处理文件滚动等逻辑
  • 对外接口:提供简单的日志输出接口,供主线程调用提交日志

线程安全日志队列实现

日志队列需要保证多线程操作时的安全性,这里使用互斥锁和条件变量实现,当队列为空时工作线程阻塞等待,当主线程提交日志后唤醒工作线程。

#include <queue>
#include <mutex>
#include <condition_variable>
#include <string>

class LogQueue {
private:
    std::queue<std::string> queue_;
    std::mutex mutex_;
    std::condition_variable cond_;
public:
    // 提交日志到队列
    void push(const std::string& log) {
        std::lock_guard<std::mutex> lock(mutex_);
        queue_.push(log);
        cond_.notify_one(); // 唤醒等待的工作线程
    }

    // 从队列取出日志,队列为空时阻塞
    std::string pop() {
        std::unique_lock<std::mutex> lock(mutex_);
        // 队列为空时等待条件变量
        cond_.wait(lock, [this]() { return !queue_.empty(); });
        std::string log = queue_.front();
        queue_.pop();
        return log;
    }

    // 判断队列是否为空
    bool empty() {
        std::lock_guard<std::mutex> lock(mutex_);
        return queue_.empty();
    }
};

后台工作线程与日志写入实现

工作线程启动后循环从日志队列取日志,然后写入到文件中,同时需要处理日志文件的创建、切换和关闭逻辑。

#include <thread>
#include <fstream>
#include <chrono>
#include <ctime>
#include <iostream>

class AsyncFileLogger {
private:
    LogQueue log_queue_;
    std::thread worker_thread_;
    std::ofstream log_file_;
    std::string log_file_path_;
    bool running_;
    // 日志文件最大大小,这里设置为10MB
    const size_t MAX_FILE_SIZE = 10 * 1024 * 1024;

    // 工作线程执行函数
    void worker_func() {
        while (running_) {
            std::string log_content = log_queue_.pop();
            // 检查日志文件是否需要切换
            if (!log_file_.is_open() || get_file_size() > MAX_FILE_SIZE) {
                rotate_log_file();
            }
            // 写入日志内容,添加时间戳
            auto now = std::chrono::system_clock::now();
            time_t now_time = std::chrono::system_clock::to_time_t(now);
            struct tm* local_tm = localtime(&now_time);
            char time_buf[64];
            strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", local_tm);
            log_file_ << "[" << time_buf << "] " << log_content << std::endl;
            log_file_.flush(); // 立即刷新到磁盘
        }
        // 退出前把队列中剩余的日志写完
        while (!log_queue_.empty()) {
            std::string log_content = log_queue_.pop();
            if (log_file_.is_open()) {
                auto now = std::chrono::system_clock::now();
                time_t now_time = std::chrono::system_clock::to_time_t(now);
                struct tm* local_tm = localtime(&now_time);
                char time_buf[64];
                strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", local_tm);
                log_file_ << "[" << time_buf << "] " << log_content << std::endl;
            }
        }
        if (log_file_.is_open()) {
            log_file_.close();
        }
    }

    // 获取当前日志文件大小
    size_t get_file_size() {
        if (!log_file_.is_open()) return 0;
        log_file_.seekp(0, std::ios::end);
        size_t size = log_file_.tellp();
        log_file_.seekp(0, std::ios::beg);
        return size;
    }

    // 切换日志文件,按时间生成新文件名
    void rotate_log_file() {
        if (log_file_.is_open()) {
            log_file_.close();
        }
        auto now = std::chrono::system_clock::now();
        time_t now_time = std::chrono::system_clock::to_time_t(now);
        struct tm* local_tm = localtime(&now_time);
        char time_buf[64];
        strftime(time_buf, sizeof(time_buf), "%Y%m%d_%H%M%S", local_tm);
        log_file_path_ = "app_log_" + std::string(time_buf) + ".log";
        log_file_.open(log_file_path_, std::ios::app);
        if (!log_file_.is_open()) {
            std::cerr << "Failed to open log file: " << log_file_path_ << std::endl;
        }
    }

public:
    AsyncFileLogger() : running_(false) {}

    // 启动日志系统
    void start(const std::string& base_path = "") {
        running_ = true;
        // 初始化第一个日志文件
        auto now = std::chrono::system_clock::now();
        time_t now_time = std::chrono::system_clock::to_time_t(now);
        struct tm* local_tm = localtime(&now_time);
        char time_buf[64];
        strftime(time_buf, sizeof(time_buf), "%Y%m%d_%H%M%S", local_tm);
        log_file_path_ = base_path + "app_log_" + std::string(time_buf) + ".log";
        log_file_.open(log_file_path_, std::ios::app);
        if (!log_file_.is_open()) {
            std::cerr << "Failed to open log file: " << log_file_path_ << std::endl;
        }
        // 启动工作线程
        worker_thread_ = std::thread(&AsyncFileLogger::worker_func, this);
    }

    // 停止日志系统
    void stop() {
        running_ = false;
        // 唤醒工作线程,使其退出循环
        log_queue_.push(""); // 推送一个空日志唤醒等待的线程
        if (worker_thread_.joinable()) {
            worker_thread_.join();
        }
    }

    // 对外提供的写日志接口
    void write_log(const std::string& log) {
        if (running_) {
            log_queue_.push(log);
        }
    }

    ~AsyncFileLogger() {
        if (running_) {
            stop();
        }
    }
};

使用示例

下面是使用该异步日志系统的示例代码,主线程可以并发提交日志,不会阻塞主线程执行。

#include <iostream>
#include <vector>
#include <thread>

int main() {
    AsyncFileLogger logger;
    // 启动日志系统
    logger.start();

    // 主线程提交日志
    logger.write_log("Main thread: program start");

    // 模拟多个线程并发写日志
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back([i, &logger]() {
            for (int j = 0; j < 10; ++j) {
                logger.write_log("Thread " + std::to_string(i) + " write log: " + std::to_string(j));
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
        });
    }

    // 主线程继续执行其他逻辑,不会被日志写入阻塞
    for (int i = 0; i < 3; ++i) {
        logger.write_log("Main thread: doing other work " + std::to_string(i));
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }

    // 等待所有子线程完成
    for (auto& t : threads) {
        if (t.joinable()) {
            t.join();
        }
    }

    logger.write_log("Main thread: program end");
    // 停止日志系统
    logger.stop();
    return 0;
}

注意事项

在实际使用中需要注意以下几点:

  • 日志队列的push操作如果队列无上限,极端情况下可能导致内存占用过高,可以根据需求给队列添加最大长度限制,超出时可以选择丢弃日志或者阻塞生产者
  • 日志文件的路径需要确保有写入权限,文件滚动策略可以根据实际需求调整,比如按天滚动或者按大小滚动
  • 程序退出前必须调用stop方法,否则可能导致部分日志没有写入文件就丢失
  • 如果日志内容包含特殊字符,需要根据需求做转义处理,避免写入文件后出现格式问题

C++11_thread异步非阻塞文件日志系统日志队列修改时间:2026-07-11 02:57:50

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。