原文地址
https://www.cnblogs.com/bourneli/archive/2011/12/28/2305280.html (修改了一点内容)
本文目的
前几天在开发中遇到一个古怪的问题,定位了两天左右的时间才发现问题。该问题正如题目所描述:单一模式在动态链接库之间出现了多个实例。由于该实例是一个配置管理器,许多配置信息都在这个实例的初始化过程中读取,一旦出错,系统的其他地方都无法正确运行,所以给问题定位带来一定难度。为了避免敏感信息的泄漏,同时为了便于大家理解,将问题简化,在此与大家分享。
问题描述
首先,编写一个简单的单一模式的类,文件singleton.h内容如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#ifndef SINGLETON_H_ #define SINGLETON_H_ class singleton{ private : singleton() {num = -1;} static singleton* pInstance; public : static singleton& instance(){ if (NULL == pInstance){ pInstance = new singleton(); } return *pInstance; } public : int num; }; singleton* singleton::pInstance = NULL; #endif |
接下来,编写一个简单的插件,调用该singleton,插件内容在hello.cpp文件中,如下:
1
2
3
4
5
6
7
8
9
|
#include <iostream> #include "singleton.h" extern "C" void hello() { std::cout << "singleton.num in hello.so : " << singleton::instance().num << std::endl; ++singleton::instance().num; std::cout << "singleton.num in hello.so after ++ : " << singleton::instance().num << std::endl; } |
最后,编写一个main函数,调用singleton和插件,如下面的example1.cpp文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
#include <iostream> #include <dlfcn.h> #include "singleton.h" int main() { using namespace std; // call singleton firstly singleton::instance().num = 100; cout << "singleton.num in main : " << singleton::instance().num << endl; // open the library void * handle = dlopen( "./hello.so" , RTLD_LAZY); if (!handle) { cerr << "Cannot open library: " << dlerror() << '
' ; return 1; } // load the symbol typedef void (*hello_t)(); // reset errors dlerror(); hello_t hello = (hello_t) dlsym(handle, "hello" ); const char *dlsym_error = dlerror(); if (dlsym_error) { cerr << "Cannot load symbol 'hello': " << dlsym_error << '
' ; dlclose(handle); return 1; } hello(); // call method in the plugin // call singleton secondly cout << "singleton.num in main : " << singleton::instance().num << endl; dlclose(handle); } |
好了,问题重现的简化版本构建完成,接下来,我们需要编译并执行它。编写makefile,内容如下(PS: singleton.h,hellp.cpp和example1.cpp在同一个目录中):
1
2
3
4
5
6
7
|
example1: main.cpp hello.so $(CXX) $(CXXFLAGS) -o example1 main.cpp -ldl hello.so: hello.cpp $(CXX) $(CXXFLAGS) -shared -o hello.so hello.cpp clean: rm -f example1 hello.so .PHONY: clean |
make完后,运行example1文件。读到这里,你认为会输出什么?不读题目,可能,你认为会输出这些内容:
singleton.num in main : 100 singleton.num in hello.so : 100 singleton.num in hello.so after ++ : 101 singleton.num in main : 101 |
可是,小黑框中输出了如下的内容(注意红色部分):
singleton.num in main : 100 singleton.num in hello.so : -1 singleton.num in hello.so after ++ : 0 singleton.num in main : 100 |
从输出内容中,可以看出singleton出现了两个实例,一个实例在main函数中,另一个实例在插件hello.so中。
原因分析和解决方法
上面的现象,与现实中的项目一样,为单例构造了两个实例,这与我的初衷(使用单一的配置)相悖。为什么会出现这个现象呢?
究其原因,是由于插件的动态链接引起的。hello.so在动态链接过程中,没有发现example1中有singleton::instance这个实例(但是事实上,该实例已经存在了),那么就会自己创建一个实例(正如单例的逻辑实现),这样就导致了两个实例。
可以通过工具nm查看example1的符号表(symbol table),看看其中是否包含singleton::instance符号(dynamic )。
(PS: google一下”symbol table”和”nm”可以了解更多细节)
$ nm -C example1 | grep singleton 080488fa t global constructors keyed to _ZN9singleton9pInstanceE 08048ab6 W singleton::instance() 08049ff0 B singleton::pInstance 08048aa8 W singleton::singleton() $ nm –C –D example1 | grep singleton $ |
D选项用于查看动态符号(dynamic symbol),你会发现singleton::pInstance在静态表中存在,在动态表中不存在,这也就是说,动态连接器(dynamic linker)在加载hello.so的时候,无法找到singleton::pInstance的唯一实例,只能构造一个新的实例,该实例在hello.so中是唯一的,但是不能保证在example1和hello.so中唯一。
现在,解决问题的关键在于如何暴露example1中的singleton::pInstance。好在,GNU make有一个链接选项-rdynamic,可以帮我们解决这个问题,看看修改后的makefile(注意第二行末尾与原来的区别):
1
2
3
4
5
6
7
|
example1: main.cpp hello.so $(CXX) $(CXXFLAGS) -o example1 main.cpp –ldl -rdynamic hello.so: hello.cpp $(CXX) $(CXXFLAGS) -shared -o hello.so hello.cpp clean: rm -f example1 hello.so .PHONY: clean |
修改后,重新编译example1(make clean && make)。
此时,再看看example1的符号表(注意红色高亮部分):
$ nm -C example1 | grep singleton 08048ada t global constructors keyed to _ZN9singleton9pInstanceE 08048c96 W singleton::instance() 08049280 B singleton::pInstance 08048c88 W singleton::singleton() $ nm -C -D example1 | grep singleton 08048c96 W singleton::instance() 08049280 B singleton::pInstance 08048c88 W singleton::singleton() |
静态符号没有什么变化,但是动态符号却显示了更多的内容,其中包括我们想要的singleton::pInstance,也就是singleton的唯一实例。
OK,此时上面的问题已经解决,执行example1,输出结果如下:
singleton.num in main : 100 singleton.num in hello.so : 100 singleton.num in hello.so after ++ : 101 singleton.num in main : 101 |
此结果表明singleton在example1和hello.so之间只产生了一个实例。
插件设计建议
不要直接对外直接暴露库的实例化接口
如a.so 内Aclass::Instance();
而是可以在a.so封装一层,提供一个函数接口如
Aclass GetInstace()
{
return Aclass::Instance();
}
这样即使b.so和c.so的GetInstace这个接口地址不是一个,但是GetInstace内部都是a.so符号信息,可以找到已经实例化的对象
参考资料
上面的解决方案是通过在stackOverflow中提问,得到的,本人只是将其翻译并总结,所以最后需要感谢一下stackOverflow中的热心的朋友,BourneLi是我的stackOverflow中的ID。问题链接如下: