Leaky singletons的一种使用场景

Leaky singletons的一种使用场景

场景

最近遇到了这个问题,正好想记录下。
比如你有一段代码,如下(伪代码):

static std::map<int, std::string> g_test_map;

class Test {
 public:
   ~Test() {}  // 析构里对线程做join动作
   bool Init() {} // 启动多个线程对g_test_map进行读写操作
};

你在一个文件中定义了一个static map静态变量,并且你的代码被编译成test.so。
重点来了,这个test.so是以共享库的形式被别人使用,而且是通过dlopen、dlclose的形式加载和卸载你的test.so。

使用方代码如下(伪代码):

std::shared_ptr<Test> LoadTest() {
	dlopen("./test.so");
	std::shared_ptr<Test> test = std::make_shared<Test>();
	test->Init();
	return test;
}

UnLoadTest() {
	test.reset();
	dlclose("./test.so");
}

使用方1在dlopen你的test.so的时候,会申请出这个static map的内存空间,你的test.so中会另外启动多个线程在对这个map进行读写操作。并且会将Test的shared_ptr返回给使用方2持有。

这里就会形成如下关系图:
使用方2->(load) 使用方1的.so -> 使用方1 (dlopen) test.so。

那在使用方2 退出的时候,使用方1会先reset 它保存的test的shared_ptr,然后再dlclose。

这里就会出现问题:

  1. 使用方1在reset test的时候,只是会将test的引用技术减1,此时使用方2还持有着test,所以test并不会析构,也不会触发线程join;
  2. 紧接着使用方1开始 dlclose,并且dlclose的时候会将g_test_map析构掉;
  3. 此时由于test还未析构,所以test中的线程还在run,这个时候就会出现访问已析构的内存,产生coredump。

这个问题如果用asan跑下,可以很明显看出asan提示的

AddressSanitizer: heap-use-after-free on address
READ of size 8 at  0xffff8e408f90 thread T9:

0xffff8e408f90 freed by thread T0 here:

这段的意思就是堆内存释放后又被使用,0xffff8e408f90 这个地址在T0被释放,又在T9被使用。

问题本质

所以综上所述,这个问题的本质是dlclose的时候静态全局变量被析构,但是存在running的线程还在使用这块全局内存。

这里有人会说,那dlclose的时候应该将test清理干净,这样就不会存在这种场景。这种说法没问题,但是这里Test的线程是在Test析构的时候join的,而Test是以shared_ptr的形式返回,并且又被使用方2持有着。所以这里场景下使用方1本质控制不了test的析构。

如何解决

  1. 第一种方法可以将static map封装成类,然后用shared_ptr包装起来,别的线程要读写这个map,就必须保存一份map的shared_ptr类,这样就能保存最后释放。
  2. 第二种方法也是之前遇到glog0.4中的一个问题,参考glog的修复手段。
    glog中的问题也是进程退出的时候static mutex被释放了,但是别的线程还在使用这个mutex,可以看下这个:
    glog0.4.0版本存在此问题,static google::log_mutex destroyed on exit, while other still existing threads want to log。具体可看如下链接:
https://github.com/google/glog/issues/504

glog里使用了Leaky singletons这种方式解决了此问题。

Leaky singletons

Leaky singletons很好理解,翻译成中文就是泄漏式单例,代码如下:

static Singleton& instance() {
    /** 以前的实现
    static Singleton inst;
    return inst;
    */
    static Singleton* inst = new Singleton; // 返回指针而不是整个对象
    return *inst;
}

用静态指针替换了静态对象,这样进程启动只会new一份,并且不会手动释放,而是进程退出的时候被系统回收内存。

这里并不是真实泄露,最终内存也会随着进程退出被系统回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值