问题描述
使用python3.6 运行项目的时候报以下错误
问题:OSError: dlopen: cannot load any more object with static TLS
操作系统错误:dlopen:无法再使用静态TLS加载任何对象
解决过程
看到这个错误,一头雾水,于是开始逐步查原因:
(1)根据错误回溯信息,查看源码
vim /soft/python3.6/lib/python3.6/site-packages/lightgbm/basic.py + 29
此处是使用python的标准库 ctypes来加载共享库,增加打印 lib_path[0],重新运行:
查看共享库是否存在:是存在的
/soft/python3.6/lib/python3.6/site-packages/lightgbm/lib_lightgbm.so
ll /soft/python3.6/lib/python3.6/site-packages/lightgbm
使用python交互式来加载这个库:
加载正常
(2)一顿操作之后,陷入了困境:单独加载动态共享库能成功,但是在程序中加载就会失败
(3)此时回归最初的问题
问题:OSError: dlopen: cannot load any more object with static TLS
操作系统错误:dlopen:无法再使用静态TLS加载任何对象
现在我们需要去了解以下东西:
<1>、static TLS 是啥?
TLS(thread local storage)线程本地存储,在一个进程中,所有的线程是共享同一个地址空间的。这样的话 如果有一个变量是全局或者是静态的,那么多有线程访问的是内存中的同一份,意味着某一个线程对其进行了修改,也会影响其他所有线程。当然有时候这不是我们所希望看到的,这时候可以使用基于堆栈的自动变量、函数参数来访问数据。不过有些时候(比如可能是特定设计的dll,so 等动态库),我们就是需要依赖全局变量或者静态变量,那有没有办法保证在多线程程序中能访问而不互相影响呢?答案是有的。操作系统帮我们提供了这个功能——TLS线程本地存储。TLS的作用是能将数据和执行的特定的线程联系起来。
实现TLS有两中方法:静态TLS 和 动态 TLS
现在我们知道了,首先是TLS是操作系统的提供的东西,而我们的错误 恰好就是 OSError
<2>、dlopen
dlopen()是一个计算机函数,功能是以指定模式打开指定的动态链接库文件。
所以ctypes 加载动态库时,肯定使用的是系统函数dlopen
<3>、回归错误:any more : 无法再使用静态TLS加载任何对象
这个再使用,意味着前面有些库时加载成功的
(4)怀疑:操作系统是不是对动态库的加载有限制,导致不能再加载了,这样也能解释 我们单独加载库的时候是正常的
最后确定了:Linux对可以加载到进程中的带有TLS(线程本地存储,以支持c++的线程存储类)的共享库的数量有一个静态限制。这个限制很小,比如14或32,这取决于操作系统版本(重要的是glibc的版本)。
(5)使用操作系统版本相同的干净机器运行程序:是正常的,排除了操作系统版本的原因
(6)查看glibc的版本,根据动态库加载路径来查看
版本是2.8 而干净系统上的版本是 2.17
这时候想起来了:之前安装其他软件时更新过这台机器的libc库。
总结
问题原因:程序引导太多使用了 静态TLS 的动态库,而linux 系统对于进程可加载的TLS 库,有限制
解决方法:方法1、查出哪些库使用了 静态TLS ,并减少这些库的加载,使用其他方式去加载
方法2、增加系统的限制数,可以多加载一些库,不同的操作系统版本和glibc 的版本的限制数都会不同