在python程序中通过ctypes使用共享库和线程本地存储时内存泄漏

时间:2011-11-10 08:35:00

标签: python memory-leaks ctypes thread-local-storage

我在python中使用ctypes模块来加载包含线程本地存储的共享c库。它是一个历史悠久的大型c库,我们正在努力使线程安全。该库包含许多全局变量和静态,因此我们对线程安全的初始策略是使用线程本地存储。我们希望我们的libarary与平台无关,并且在win32,win64和64位Ubuntu上编译和测试线程安全性。从纯粹的c-process开始,似乎没有任何问题。

但是在win32和Ubuntu上的python(2.6和2.7)中,我们看到了内存泄漏。当python线程终止时,似乎线程本地存储没有被正确释放。或者至少在某种程度上,python进程没有“意识到”内存被释放。实际上在win32上的c#-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;}

在linux上的windows / gcc上编译机智MINGW,如:

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())

这足以重现错误。显式导入垃圾收集器,在启动每个新线程时执行collect gc.collect()没有帮助。

有一段时间我认为问题与不兼容的运行时有关(使用Visual Studio编译的python,我的库MINGW)。但问题也在于Ubuntu,但在win64服务器上却没有,即使库与MINGW交叉编译也是如此。

希望有人能提供帮助!

干杯, Simon Kokkendorff,丹麦全国调查和地籍。

2 个答案:

答案 0 :(得分:3)

这似乎不是ctypes'或Python的错。通过只写C代码,我可以重现相同的泄漏,以相同的速率泄漏。

奇怪的是,至少在Ubuntu Linux 64上,如果带有__thread变量的Leaker()函数被编译为.so并从具有dlopen()的程序调用,则会发生泄漏。运行完全相同的代码但两个部分一起编译为常规C程序时不会发生。

我怀疑故障是动态链接库和线程本地存储之间的一些交互。不过,它看起来像一个相当糟糕的错误(它真的没有文档吗?)。

答案 1 :(得分:1)

我的猜测是没有加入线程就是问题所在。从pthread_join的手册页:

  

无法加入可加入的线程(即不加入的线程)   分离),产生一个“僵尸线程”。避免这样做,因为每个   僵尸线程消耗一些系统资源,并在足够的僵尸   线程已经积累,将不再可能创建新的   线程(或进程)。

如果修改循环以收集线程对象并在最后一个while循环中对它们使用.isAlive()和.join(),我认为应该注意你的内存泄漏。