- c++11的
的create implement是在thread.cc 中实现的,这意味着创建代码在libstdc++.so 中,创建代码需要使用与平台有关的api - gcc(g++ is a part of gcc)的预期:
- 没有调用的thread的代码,不会产生对pthread的依赖,更重要的,不同配置的gcc的线程模型是不同的,依赖库也不同(即不一定是pthread),如果不去除依赖,这会导致链接的深刻耦合
- 调用了thread的代码,必须要链接到pthread
- gcc 内部,通过弱符号机制来达到这个目的,以foo函数举例:
- 通过一个包装的符号,如gcc_foo, 弱引用到foo
- 声明foo 是一个弱符号,可以在链接时被强符号替代,弱符号默认是未定义的(可能也不是空指针)
- g++的thread.cc 通过gcc_foo 包装后的函数,来创建线程,而不是直接使用平台api
通过弱符号,即便是业务代码没有链接pthread,thread.cc 相关的代码也不会产生链接报错。那么,g++ 又是如何完成2.2. 的呢? 答案是g++ 通过一个无用参数强制产生对pthread 的依赖,这部分实现是在
那么为什么dlopen 又会导致使用了std::thread的库crash呢? 这是因为符号加载顺序的问题,libdl 不是ld,他是glibc的一部分,他通过名字空间等机制支持符号的隔离等。名字空间一般有:local/global 以及其他(可能和加载顺序有关),dlopen 加载一个库时,其查找符号的顺序是:
- LOCAL
- GLOBAl
- 其他
前面说过,因为gcc内部搞了一个弱符号,他是存在应用程序的符号表中的,应用程序的符号表对于dlopen的so而言是全局的。可以明确的是LOCAL肯定没有pthread相关符号,GLOBAL中有gcc定义的弱符号,dl认为找到了,但实际是错误的,从而导致了不能debug的crash
其实最后的结论并不正确,因为这是一个推论,并非从代码或者实践得出的结论。其实可以再思考一下,就能发现其中的问题:
- libstdc++.so 在 业务代码中已经被加载一次了,符号也和业务代码符号合并了,dlopen 并不会再次重新加载libstdc++.so
- 真正需要可用符号的时stdc++中创建线程的实现代码,而不是我们的so代码
所以这里的问题是:需要刷新 libstdc++.so 中的弱符号,而因为应用先加载了libstdc++.so了,dlopen 加载so时不会刷新其中的符号,同时so使用了stdc++中相关实现,进而导致了使用到了弱符号。
我们验证这个猜想很简单,我们编写一个C的应用来加载这个so,这样,libstdc++的符号就能在 dlopen时刷新了, 那么其就不会crash,详见测试代码main.c
在After
阶段后,so中的线程正常被创建,一切OK
不过两者从表面来看推测都正确,但是结论完全不同。