MST
星途 面试题库

面试题:C++多线程程序中,特定线程崩溃后如何保证进程内关键业务逻辑的原子性与一致性

在一个复杂的C++多线程应用程序中,有多个线程协同完成一系列关键业务逻辑(如数据库事务操作、数据一致性维护等)。若其中一个参与关键业务逻辑的线程崩溃,如何确保整个关键业务逻辑的原子性与一致性?请从操作系统原理、C++线程同步机制以及相关数据结构等方面详细阐述解决方案,并提供详细的代码设计思路和可能涉及的关键代码片段。
10.7万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

操作系统原理层面

  1. 进程与线程模型:理解进程是资源分配的基本单位,线程是CPU调度的基本单位。在多线程应用中,一个进程内的所有线程共享进程的资源。当一个线程崩溃时,操作系统通常会终止整个进程(取决于操作系统的实现和设置)。为避免这种情况,可以考虑将关键业务逻辑放在单独的进程中,通过进程间通信(IPC)机制(如管道、共享内存、消息队列等)与其他进程协作。这样即使某个进程崩溃,其他进程仍能正常运行。
  2. 信号处理:操作系统提供信号机制,当线程出现异常(如段错误等导致崩溃的情况),会发送相应信号。可以注册信号处理函数,在信号处理函数中进行一些清理操作,并尝试恢复关键业务逻辑,例如回滚未完成的数据库事务。

C++线程同步机制层面

  1. 互斥锁(Mutex):在关键业务逻辑的代码段前后使用互斥锁来保证同一时间只有一个线程能进入该代码段。例如:
std::mutex mtx;
void critical_section() {
    mtx.lock();
    // 关键业务逻辑代码
    mtx.unlock();
}
  1. 条件变量(Condition Variable):用于线程间的同步,当某个条件满足时通知等待的线程。在关键业务逻辑中,若需要等待某个条件(如数据准备好)才能继续执行,可以使用条件变量。
std::mutex mtx;
std::condition_variable cv;
bool data_ready = false;
void thread_function() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{return data_ready;});
    // 关键业务逻辑代码
}
void set_data_ready() {
    std::unique_lock<std::mutex> lock(mtx);
    data_ready = true;
    cv.notify_one();
}
  1. 读写锁(Read - Write Lock):如果关键业务逻辑存在读多写少的情况,可以使用读写锁,允许多个线程同时读,但写操作时需要独占。
std::shared_mutex rw_mutex;
void read_operation() {
    std::shared_lock<std::shared_mutex> lock(rw_mutex);
    // 读操作代码
}
void write_operation() {
    std::unique_lock<std::shared_mutex> lock(rw_mutex);
    // 写操作代码
}

相关数据结构层面

  1. 事务日志(Transaction Log):类似于数据库的事务日志,记录关键业务逻辑的每一步操作。当线程崩溃时,可以根据事务日志进行回滚或恢复操作。例如,可以设计一个简单的日志类:
class TransactionLog {
public:
    void log(const std::string& operation) {
        std::lock_guard<std::mutex> lock(mtx);
        log_entries.push_back(operation);
    }
    std::vector<std::string> get_log() {
        std::lock_guard<std::mutex> lock(mtx);
        return log_entries;
    }
private:
    std::vector<std::string> log_entries;
    std::mutex mtx;
};
  1. 版本控制数据结构:对于数据一致性维护,可以使用带有版本号的数据结构。每次对数据进行修改时,版本号递增。其他线程在读取数据时,不仅读取数据,还读取版本号,通过比较版本号来判断数据是否是最新的。

代码设计思路

  1. 模块化设计:将关键业务逻辑划分为多个模块,每个模块负责一个具体的功能,如数据库操作模块、数据一致性维护模块等。每个模块可以使用上述的线程同步机制来保证自身的原子性和一致性。
  2. 错误处理与恢复:在每个关键业务逻辑函数中,增加错误处理代码。当线程出现异常时,捕获异常并进行相应的处理,如回滚事务、记录错误日志等。同时,可以使用C++的RAII(Resource Acquisition Is Initialization)机制来确保资源的正确释放。
  3. 监控与恢复线程:创建一个监控线程,定期检查关键业务逻辑相关线程的状态。当发现某个线程崩溃时,监控线程可以尝试重新启动该线程,并根据事务日志和版本控制数据结构来恢复关键业务逻辑的一致性。

关键代码片段示例

  1. 使用互斥锁保证数据库事务原子性
class Database {
public:
    void begin_transaction() {
        mtx.lock();
    }
    void commit_transaction() {
        mtx.unlock();
    }
    void execute_query(const std::string& query) {
        // 执行数据库查询
    }
private:
    std::mutex mtx;
};
  1. 使用事务日志进行回滚
TransactionLog log;
Database db;
void critical_business_logic() {
    try {
        db.begin_transaction();
        log.log("Start transaction");
        // 数据库操作
        db.execute_query("INSERT INTO table VALUES (...)");
        log.log("Insert operation");
        db.commit_transaction();
        log.log("Commit transaction");
    } catch(...) {
        auto entries = log.get_log();
        // 根据日志进行回滚操作
        db.begin_transaction();
        for (auto it = entries.rbegin(); it != entries.rend(); ++it) {
            if (*it == "Insert operation") {
                db.execute_query("DELETE FROM table WHERE...");
            }
        }
        db.commit_transaction();
    }
}
  1. 监控线程示例
std::atomic<bool> thread_crashed(false);
std::thread monitor_thread([&] {
    while (true) {
        // 检查关键业务逻辑线程的状态
        if (thread_crashed) {
            // 尝试重新启动线程
            std::thread new_thread(critical_business_logic);
            new_thread.detach();
            thread_crashed = false;
        }
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
});