12

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

然而,在 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;}

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

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()在启动每个新线程时执行 a 无济于事。

有一段时间我认为问题与不兼容的运行时有关(python 使用 Visual Studio 编译,我的库使用MINGW. 但问题也出现在 Ubuntu 上,但不在 win64 服务器上,即使库是使用MINGW.

希望任何人都可以提供帮助!

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

4

2 回答 2

3

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

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

我怀疑故障是动态链接库和线程本地存储之间的一些交互。尽管如此,它看起来还是一个相当糟糕的错误(它真的没有记录吗?)。

于 2012-07-26T23:47:33.387 回答
1

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

未能与可连接的线程(即未分离的线程)连接会产生“僵尸线程”。避免这样做,因为每个僵尸线程都会消耗一些系统资源,当累积了足够多的僵尸线程时,将无法再创建新线程(或进程)。

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

于 2011-11-29T05:01:27.433 回答