MST

星途 面试题库

面试题:C++ 智能指针异常安全与多线程场景

在多线程环境下,std::shared_ptr 被广泛用于管理共享资源。请详细阐述在涉及异常处理时,std::shared_ptr 如何保证线程安全,以及在资源释放过程中可能遇到的竞争条件和解决方案。如果存在多个线程同时对同一个 std::shared_ptr 进行操作并可能抛出异常,如何设计代码以确保整体的异常安全性和资源正确管理?
28.8万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

std::shared_ptr 在异常处理下的线程安全

  1. 引用计数的原子操作std::shared_ptr 通过原子引用计数机制来管理资源。在多线程环境下,引用计数的增减操作都是原子的。这意味着,无论有多少线程同时对 std::shared_ptr 进行赋值、拷贝或析构操作,引用计数的修改都不会出现数据竞争。例如,当一个线程创建 std::shared_ptr 指向某个资源时,引用计数原子地增加;当 std::shared_ptr 离开作用域或被重新赋值时,引用计数原子地减少。这种原子性确保了在多线程环境下引用计数的一致性。
  2. 资源释放的线程安全:由于引用计数的原子操作,当引用计数降为 0 时,资源的释放操作也是线程安全的。多个线程同时观察到引用计数为 0 时,只有一个线程会执行资源释放的操作(例如调用 delete),其他线程会因为原子操作的特性而不会重复释放资源。

资源释放过程中的竞争条件及解决方案

  1. 竞争条件:虽然引用计数本身是线程安全的,但在资源释放的具体操作(如调用自定义的删除器)中仍可能存在竞争条件。例如,如果自定义删除器需要访问共享资源(除了要释放的目标资源外),而这个共享资源没有适当的同步机制,就可能出现竞争。
  2. 解决方案
    • 使用互斥锁:在自定义删除器中,如果涉及到访问共享资源,可以使用互斥锁来保护共享资源的访问。例如:
std::mutex resourceMutex;
void customDeleter(MyResource* res) {
    std::lock_guard<std::mutex> lock(resourceMutex);
    // 进行资源释放操作
    delete res;
}
std::shared_ptr<MyResource> ptr(new MyResource(), customDeleter);
- **无状态删除器**:尽量使用无状态的删除器,避免删除器内部出现共享资源的访问。例如,使用默认的 `delete` 操作作为删除器,这样就不存在因删除器内部共享资源访问导致的竞争条件。

多线程操作并可能抛出异常时的代码设计

  1. 异常安全性:为了确保异常安全性,在对 std::shared_ptr 进行操作时,应尽量使用异常安全的函数。例如,使用 std::make_shared 来创建 std::shared_ptr,因为它是异常安全的。std::make_shared 一次性分配内存用于控制块(包含引用计数等信息)和对象本身,减少了内存分配失败导致资源泄漏的风险。
  2. 资源正确管理
    • RAII 原则:始终遵循 RAII(Resource Acquisition Is Initialization)原则,将资源的管理封装在 std::shared_ptr 中。例如,在函数内部创建 std::shared_ptr 来管理局部资源,这样当函数因为异常退出时,std::shared_ptr 的析构函数会自动释放资源。
    • 异常安全的函数设计:如果函数内部涉及多个对 std::shared_ptr 的操作,且这些操作可能抛出异常,应确保函数整体是异常安全的。一种方法是使用 try - catch 块捕获可能抛出的异常,并在捕获异常后进行适当的清理操作。例如:
void threadSafeFunction() {
    std::shared_ptr<MyResource> ptr;
    try {
        ptr = std::make_shared<MyResource>();
        // 对 ptr 进行其他操作,这些操作可能抛出异常
        ptr->doSomething();
    } catch(...) {
        // 处理异常,此时 ptr 会自动释放资源
        std::cerr << "Exception caught" << std::endl;
    }
}

通过以上方式,可以在多线程环境下确保 std::shared_ptr 的异常安全性和资源的正确管理。