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
相关产品推荐
相关产品推荐

