You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

std::weak_ptr::lock()与析构函数执行时机冲突的处理方案咨询

问题:确保std::weak_ptr关联资源完全释放后再创建新实例

业务场景中使用std::weak_ptr缓存资源,外部通过std::shared_ptr控制资源生命周期,要求申请新资源时必须等待上一次的资源完全释放(包括析构函数执行完毕)。但存在一个问题:std::weak_ptr::lock()返回空值仅说明关联的std::shared_ptr引用计数已归零,不代表资源的析构函数已经执行完成,可能导致旧资源析构未结束时就创建新资源。

演示代码

#include <iostream>
#include <thread>
#include <memory>

using std::cout;
using std::endl;

struct A {
    A() {
        cout << "A apply resource" << endl;
    }
    ~A() {
        cout << "A release begin" << endl;
        // 模拟耗时的资源释放操作
        std::this_thread::sleep_for(std::chrono::seconds(2));
        cout << "A release end" << endl;
    }
};

std::weak_ptr<A> wptr;

std::shared_ptr<A> GetOrCreateA()
{
    auto ptr = wptr.lock();
    if (ptr) {
        cout << "not null" << endl;
    } else {
        // 此处可能在旧资源析构未完成时创建新资源
        auto newA = std::make_shared<A>();
        wptr = newA;
        ptr = newA;
    }
    return ptr;
}

int main()
{
    std::thread t1([]() {
        auto ptr = GetOrCreateA();
    });
    std::this_thread::sleep_for(std::chrono::seconds(1));

    auto ptr = GetOrCreateA();

    t1.join();
    cout << "main quit";
}

运行结果

A apply resource
A release begin
A apply resource
A release begin

可以看到,旧资源的析构尚未完成(未输出A release end),新资源已经被创建,违反了业务要求。


解决方案

方案1:使用条件变量+互斥锁跟踪析构完成状态

通过全局同步变量跟踪资源是否完全释放,在创建新资源前等待旧资源析构完成。

修改后的代码:

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

using std::cout;
using std::endl;

struct A {
    A() { cout << "A apply resource" << endl; }
    ~A() {
        cout << "A release begin" << endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
        cout << "A release end" << endl;
        
        // 析构完成后更新状态并通知等待线程
        std::lock_guard<std::mutex> lock(mtx);
        is_released = true;
        cv.notify_all();
    }
    
    static std::mutex mtx;
    static std::condition_variable cv;
    static bool is_released;
};

std::mutex A::mtx;
std::condition_variable A::cv;
bool A::is_released = true;

std::weak_ptr<A> wptr;

std::shared_ptr<A> GetOrCreateA()
{
    std::unique_lock<std::mutex> lock(A::mtx);
    // 等待资源完全释放
    A::cv.wait(lock, []{ return A::is_released; });
    
    auto ptr = wptr.lock();
    if (!ptr) {
        // 创建新资源前标记为未释放
        A::is_released = false;
        ptr = std::make_shared<A>();
        wptr = ptr;
    }
    return ptr;
}

int main()
{
    std::thread t1([]() {
        auto ptr = GetOrCreateA();
    });
    std::this_thread::sleep_for(std::chrono::seconds(1));

    auto ptr = GetOrCreateA();

    t1.join();
    cout << "main quit";
}

原理:用is_released标记资源是否完全释放,A的析构函数执行完毕后更新该标记并通知等待线程。GetOrCreateA中先等待标记为true,确保旧资源析构完成后才允许创建新资源。

方案2:自定义shared_ptr删除器实现同步

将同步逻辑嵌入std::shared_ptr的自定义删除器中,确保只有旧资源的析构(含删除器逻辑)完成后,才允许创建新资源。

修改后的代码:

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

using std::cout;
using std::endl;

struct A {
    A() { cout << "A apply resource" << endl; }
    ~A() {
        cout << "A release begin" << endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
        cout << "A release end" << endl;
    }
};

std::weak_ptr<A> wptr;
std::mutex mtx;
std::condition_variable cv;

void custom_deleter(A* ptr) {
    delete ptr; // 执行A的析构
    // 析构完成后通知等待线程
    std::lock_guard<std::mutex> lock(mtx);
    cv.notify_all();
}

std::shared_ptr<A> GetOrCreateA()
{
    std::unique_lock<std::mutex> lock(mtx);
    
    auto ptr = wptr.lock();
    if (ptr) {
        return ptr;
    }
    
    // 等待旧资源的删除器执行完毕
    cv.wait(lock, [&]{ 
        // 双重检查:确保weak_ptr已过期,且删除器已执行完成
        bool expired = wptr.expired();
        if (expired) {
            // 若已过期,等待直到收到删除器的通知
            cv.wait(lock);
        }
        return expired;
    });
    
    // 创建新资源,使用自定义删除器
    ptr = std::shared_ptr<A>(new A(), custom_deleter);
    wptr = ptr;
    return ptr;
}

int main()
{
    std::thread t1([]() {
        auto ptr = GetOrCreateA();
    });
    std::this_thread::sleep_for(std::chrono::seconds(1));

    auto ptr = GetOrCreateA();

    t1.join();
    cout << "main quit";
}

原理:自定义删除器在执行完A的析构后才发送通知,GetOrCreateA中等待该通知,确保旧资源完全销毁后再创建新实例。

方案3:串行化资源操作任务

将所有资源创建/销毁请求放到单线程任务队列中,强制所有操作串行执行,从根本上避免资源生命周期重叠的问题。

修改后的代码:

#include <iostream>
#include <thread>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <functional>
#include <future>

using std::cout;
using std::endl;

struct A {
    A() { cout << "A apply resource" << endl; }
    ~A() {
        cout << "A release begin" << endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
        cout << "A release end" << endl;
    }
};

std::queue<std::function<void()>> task_queue;
std::mutex queue_mtx;
std::condition_variable queue_cv;
std::thread worker;
bool stop_worker = false;

void worker_loop() {
    while (true) {
        std::unique_lock<std::mutex> lock(queue_mtx);
        queue_cv.wait(lock, []{ return !task_queue.empty() || stop_worker; });
        
        if (stop_worker && task_queue.empty()) break;
        
        auto task = std::move(task_queue.front());
        task_queue.pop();
        lock.unlock();
        
        task();
    }
}

void init_worker() {
    worker = std::thread(worker_loop);
}

void shutdown_worker() {
    std::lock_guard<std::mutex> lock(queue_mtx);
    stop_worker = true;
    queue_cv.notify_all();
    worker.join();
}

std::weak_ptr<A> wptr;

std::future<std::shared_ptr<A>> GetOrCreateA() {
    std::promise<std::shared_ptr<A>> prom;
    auto fut = prom.get_future();
    
    std::lock_guard<std::mutex> lock(queue_mtx);
    task_queue.push([prom = std::move(prom)]() mutable {
        auto ptr = wptr.lock();
        if (!ptr) {
            ptr = std::make_shared<A>();
            wptr = ptr;
        }
        prom.set_value(ptr);
    });
    queue_cv.notify_one();
    
    return fut;
}

int main()
{
    init_worker();
    
    std::thread t1([]() {
        auto fut = GetOrCreateA();
        auto ptr = fut.get();
    });
    std::this_thread::sleep_for(std::chrono::seconds(1));

    auto fut = GetOrCreateA();
    auto ptr = fut.get();

    t1.join();
    shutdown_worker();
    cout << "main quit";
}

原理:所有资源操作请求都被放入单线程处理的任务队列,确保旧资源的析构完成后才会处理下一个创建请求,完全避免并发导致的生命周期重叠。


内容的提问来源于stack exchange,提问作者x f

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 04:24:52