MST

星途 面试题库

面试题:C++ std::weak_ptr在多线程环境下解决循环引用的挑战与应对

在多线程环境中,使用std::weak_ptr解决循环引用会面临哪些挑战,例如线程安全、内存同步等问题?请提出相应的解决方案,并结合C++多线程库(如std::thread、std::mutex等)给出一个完整的示例代码,展示如何安全地在多线程场景下使用std::weak_ptr解决循环引用。
23.0万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

1. 使用 std::weak_ptr 解决循环引用面临的挑战

  • 线程安全问题
    • 访问竞争:不同线程可能同时访问 std::weak_ptr 相关对象,比如一个线程正在将 std::weak_ptr 提升为 std::shared_ptr,另一个线程可能正在销毁被指向的对象,导致未定义行为。
    • 状态变更竞争:当一个线程通过 std::weak_ptr 提升为 std::shared_ptr 时,另一个线程可能正在改变对象的生命周期状态(例如通过 std::shared_ptr 释放对象),从而影响提升操作的结果。
  • 内存同步问题
    • 缓存一致性:现代处理器为提高性能,每个核心都有自己的缓存。不同线程对 std::weak_ptr 及其相关对象的读写操作可能因为缓存不一致而出现数据可见性问题。例如,一个线程更新了对象的引用计数(与 std::weak_ptr 相关),但另一个线程可能由于缓存未及时更新,看不到这个变化。

2. 相应解决方案

  • 线程安全
    • 互斥锁保护:使用 std::mutex 对涉及 std::weak_ptr 的关键操作(如提升、销毁对象等)进行加锁保护,确保同一时间只有一个线程可以执行这些操作。
    • 原子操作:对于引用计数等关键的内存操作,使用原子类型(如 std::atomic)来保证操作的原子性,避免数据竞争。
  • 内存同步
    • 使用 std::memory_order:在涉及 std::weak_ptr 及其相关对象的原子操作中,指定合适的 std::memory_order,如 std::memory_order_releasestd::memory_order_acquire,以确保内存操作的顺序性和可见性。

3. 完整示例代码

#include <iostream>
#include <memory>
#include <thread>
#include <mutex>
#include <vector>

class B;

class A {
public:
    std::weak_ptr<B> weakB;
    ~A() {
        std::cout << "A destroyed" << std::endl;
    }
};

class B {
public:
    std::weak_ptr<A> weakA;
    ~B() {
        std::cout << "B destroyed" << std::endl;
    }
};

std::mutex mtx;

void threadFunction(std::shared_ptr<A>& a, std::shared_ptr<B>& b) {
    {
        std::lock_guard<std::mutex> lock(mtx);
        a = std::make_shared<A>();
        b = std::make_shared<B>();
        a->weakB = b;
        b->weakA = a;
    }

    std::this_thread::sleep_for(std::chrono::seconds(1));

    {
        std::lock_guard<std::mutex> lock(mtx);
        if (auto tempB = a->weakB.lock()) {
            std::cout << "A can access B through weak_ptr" << std::endl;
        }
        if (auto tempA = b->weakA.lock()) {
            std::cout << "B can access A through weak_ptr" << std::endl;
        }
    }
}

int main() {
    std::shared_ptr<A> a;
    std::shared_ptr<B> b;
    std::vector<std::thread> threads;
    for (int i = 0; i < 2; ++i) {
        threads.emplace_back(threadFunction, std::ref(a), std::ref(b));
    }

    for (auto& th : threads) {
        th.join();
    }

    return 0;
}

在上述代码中:

  1. 互斥锁保护:使用 std::mutexmtx)来保护对 std::shared_ptrstd::weak_ptr 的操作,确保线程安全。
  2. 对象创建与关联:在 threadFunction 中,通过 std::make_shared 创建 AB 的共享指针,并建立 std::weak_ptr 的关联。
  3. 提升操作:在临界区内,通过 lock 方法将 std::weak_ptr 提升为 std::shared_ptr,确保在提升过程中对象不会被其他线程销毁。