use*_*171 12 python ctypes memory-leaks thread-local-storage
我ctypes在python中使用模块来加载一个包含线程本地存储的共享c库.它是一个历史悠久的大型c库,我们正在努力使线程安全.该库包含许多全局变量和静态,因此我们对线程安全的初始策略是使用线程本地存储.我们希望我们的libarary与平台无关,并且在win32,win64和64位Ubuntu上编译和测试线程安全性.从纯粹的c-process开始,似乎没有任何问题.
但是在win32和Ubuntu的python(2.6和2.7)中,我们看到了内存泄漏.当python线程终止时,似乎没有正确释放线程本地存储.或者至少在某种程度上,python进程没有"意识到"内存被释放.实际上在win32上的ac#-program中也可以看到同样的问题,但是我们的win64服务器测试机器上也没有这个问题(也运行python 2.7).
这个问题可以用这样一个简单的玩具例子重现:
创建一个包含(linux/unix删除__declspec(dllexport))的c文件:
#include <stdio.h>
#include <stdlib.h>
void __declspec(dllexport) Leaker(int tid){
static __thread double leaky[1024];
static __thread int init=0;
if (!init){
printf("Thread %d initializing.", tid);
int i;
for (i=0;i<1024;i++) leaky[i]=i;
init=1;}
else
printf("This is thread: %d\n",tid);
return;}
Run Code Online (Sandbox Code Playgroud)
MINGW在Linux上的windows/gcc上编译机智如:
gcc -o leaky.dll(或leaky.so)-shared the_file.c
在Windows上,我们可以用Visual Studio编译,替换__thread为__declspec(thread).但是在win32上(我相信winXP),如果要在运行时加载库,这将不起作用LoadLibrary.
现在创建一个python程序,如:
import threading, ctypes, sys, time
NRUNS=1000
KEEP_ALIVE=5
REPEAT=2
lib=ctypes.cdll.LoadLibrary("leaky.dll")
lib.Leaker.argtypes=[ctypes.c_int]
lib.Leaker.restype=None
def UseLibrary(tid,repetitions):
for i in range(repetitions):
lib.Leaker(tid)
time.sleep(0.5)
def main():
finished_threads=0
while finished_threads<NRUNS:
if threading.activeCount()<KEEP_ALIVE:
finished_threads+=1
thread=threading.Thread(target=UseLibrary,args=(finished_threads,REPEAT))
thread.start()
while threading.activeCount()>1:
print("Active threads: %i" %threading.activeCount())
time.sleep(2)
return
if __name__=="__main__":
sys.exit(main())
Run Code Online (Sandbox Code Playgroud)
这足以重现错误.显式导入垃圾收集器,collect gc.collect()在启动每个新线程时做一个没有帮助.
有一段时间我认为问题与不兼容的运行时有关(使用Visual Studio编译的python,我的库MINGW).但问题也出现在Ubuntu上,但是在win64服务器上却没有,即使这个库是交叉编译的MINGW.
希望有人能帮忙!
干杯,Simon Kokkendorff,全国调查和丹麦地籍.
这似乎根本不是 ctypes 或 Python 的错。我可以通过仅编写 C 代码来重现相同的泄漏,以相同的速率泄漏。
奇怪的是,至少在 Ubuntu Linux 64 上,如果带有 __thread 变量的 Leaker() 函数被编译为 .so 并从程序中使用 dlopen() 调用,就会发生泄漏。当运行完全相同的代码但将两个部分编译为常规 C 程序时,不会发生这种情况。
我怀疑错误是动态链接库和线程本地存储之间的某些交互。不过,它看起来像是一个相当严重的错误(它真的没有记录吗?)。