问题描述
在项目开发过程中涉及到许多类间的包含与嵌套包含关系,类里面又启动了多个线程,出现偶现coredump问题:
terminate called after throwing an instance of ‘std::system_error’
what(): Resource deadlock avoided
问题复现
将项目中的问题抽象为A包含数据成员B(直接包含或者间接包含),B又反过来持有的A的weak_ptr,B中开启线程会持续调用A,代码如下:
#include <iostream>
#include <thread>
#include <functional>
using namespace std;
class TopObject;
class BottlemObject {
public:
BottlemObject(std::weak_ptr<TopObject> obj) : top_obj_(obj) {
thread_ = std::thread(&BottlemObject::Func, this);
std::cout << "BottlemObject construct!" << std::endl;
}
~BottlemObject() {
is_stop = true;
if(thread_.joinable()) {
thread_.join();
}
std::cout << "BottlemObject destruct!" << std::endl;
}
private:
void Func() {
while(!is_stop) {
auto obj = top_obj_.lock();
if(!obj) { return; }
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
private:
std::weak_ptr<TopObject> top_obj_;
bool is_stop{false};
std::thread thread_;
};
class TopObject : public std::enable_shared_from_this<TopObject> {
public:
TopObject() {
std::cout << "TopObject construct!" << std::endl;
}
void CreateObj() {
bot_obj_ = std::make_shared<BottlemObject>(weak_from_this());
}
~TopObject() { std::cout << "TopObject destruct!" << std::endl;}
private:
std::shared_ptr<BottlemObject> bot_obj_;
};
class Defer {
public:
Defer(std::function<void()> func) : func_(func) {}
~Defer() { func_(); }
private:
std::function<void()> func_;
};
int main() {
{
auto obj = std::make_shared<TopObject>();
obj->CreateObj();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
//这里主线程卡住2s,保证func()退出循环调用到join
std::this_thread::sleep_for(std::chrono::seconds(2));
Defer defer([]{std::cout << "main exit!!!" << std::endl;});
return 0;
}
执行结果如下:
问题分析
执行流程如下:
1、进程启动,创建TopObject对象
2、TopObject创建BottlemObject对象,BottlemObject对象持有了TopObject的weak_ptr,启动thread_并且调用Func(),Func中循环lock出TopObject的shared_ptr,持有1s
3、main中{}块退出,TopObject的shared_ptr引用计数-1,之后仅有BottlemObject对象的thread持有shared_ptr
4、BottlemObject对象的Func()退出第一次循环,TopObject对象开始析构,继续调用BottlemObject对象析构函数,接着调用thread join自身触发了coredump
解决办法
以上问题的根本原因是TopObject对象被自身创建的线程对象持有,这样其作用域结束后无法正常析构,造成资源泄露。
解决办法是,将BottlemObject的top_obj_改为TopObject*,这样在{}块结束时,会调用TopObject的析构函数,调用BottlemObject的析构,调用thread_的join(),待Func执行完以后,BottlemObject析构完成退出,TopObject析构完成退出,然后进程退出