2011-11-10 8 views
12

Estoy usando el módulo ctypes en python para cargar una biblioteca c compartida, que contiene almacenamiento local de subprocesos. Es una biblioteca c bastante grande con una larga historia, que estamos tratando de hacer seguro el hilo. La biblioteca contiene muchas variables globales y estáticas, por lo que nuestra estrategia inicial para la seguridad de subprocesos ha sido utilizar el almacenamiento local de subprocesos. Queremos que nuestra biblioteca sea independiente de la plataforma, y ​​hemos estado compilando y probando la seguridad de subprocesos tanto en win32, win64 como en Ubuntu de 64 bits. A partir de un proceso c puro, no parece haber ningún problema.Fuga de memoria cuando se utiliza una biblioteca compartida con almacenamiento local de subprocesos a través de ctypes en un programa python

Sin embargo, en python (2.6 y 2.7) en win32 y en Ubuntu estamos viendo pérdidas de memoria. Parece que el almacenamiento local de subprocesos no se está liberando correctamente cuando finaliza un subproceso de Python. O al menos que de alguna manera el proceso de Python no es "consciente" de que la memoria se libera. El mismo problema también se ve en un programa C# en win32 en realidad, pero no está presente en nuestra máquina de prueba de servidor win64 (también se ejecuta python 2.7).

El problema se puede reproducir con un simple ejemplo de juguete como esto:

Crear una c-archivo que contiene (en linux/unix eliminar __declspec(dllexport)):

#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;} 

Compilar ingenio MINGW en las ventanas/gcc en linux como:

gcc -o leaky.dll (o leaky.so) -shared the_file.c

En Windows podríamos haber compilado con Visual Studio, reemplazando __thread con __declspec(thread). Sin embargo, en win32 (hasta winXP creo), esto no funciona si la biblioteca se va a cargar en tiempo de ejecución con LoadLibrary.

A continuación, cree un programa Python como:

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

Eso es suficiente para reproducir el error. Importe explícitamente el recolector de basura, haciendo un collect gc.collect() al iniciar cada nuevo subproceso no ayuda.

Por un tiempo pensé que el problema tenía que ver con los tiempos de ejecución incompatibles (python compilado con Visual Studio, mi biblioteca con MINGW). Pero el problema también está en Ubuntu, pero no en un servidor win64, incluso cuando la biblioteca se compila de forma cruzada con MINGW.

Espero que cualquiera pueda ayudar!

Cheers, Simon Kokkendorff, Encuesta nacional y catastro de Dinamarca.

+0

por favor revise los errores conocidos pitón http://bugs.python.org/issue6627 http://bugs.python.org/issue3757 –

+0

Podría liberar sus variables de fugas en hilo cerca en C? –

+0

para arreglar esto, intente usar malloc y libre para inicializar y eliminar la matriz – pyCthon

Respuesta

3

Parece que esto no es culpa de ctypes ni de Python. Puedo reproducir la misma fuga, filtrando a la misma velocidad, escribiendo solo el código C.

Curiosamente, al menos en Ubuntu Linux 64, la fuga se produce si la función Leaker() con las variables __thread se compila como .so y se llama desde un programa con dlopen(). No ocurre cuando se ejecuta exactamente el mismo código pero con ambas partes compiladas juntas como un programa C normal.

Sospecho que el error es una cierta interacción entre las bibliotecas vinculadas dinámicamente y el almacenamiento local de subprocesos. Aún así, parece un error bastante malo (¿es realmente indocumentado?).

+0

Parece que este hilo respalda su teoría: http://sourceware.org/ml/libc-help/2011-04/msg00000.html –

+0

Sí y - y parece ser dependiente del sistema operativo. Obtengo el comportamiento en Win XP (envolviendo la biblioteca mediante python o C#, y supongo que como notas desde C) (32 bits), en Ubuntu (tanto de 32 bits como de 64 bits, creo); sin embargo, en un servidor de Windows (64 bit), no lo veo. – user1037171

1

Supongo que el problema no es unirme a los hilos. Desde la página del manual de pthread_join:

Si no se unirá a un hilo que es acoplable (es decir, uno que no se separa ), produce un "hilo zombi". Evite hacer esto, ya que cada subproceso zombi consume algunos recursos del sistema, y ​​cuando se han acumulado suficientes hilos zombie , ya no será posible crear nuevos hilos (o procesos).

Si modifica su bucle para recoger los objetos y el uso de hilo .isAlive() y .join() sobre ellos en el último bucle while creo que debe cuidar de su pérdida de memoria.

+0

Gracias por su respuesta. Aunque parece que no me uno a los hilos es el problema. Incluso si me uno a cada hilo justo después de la creación, de modo que solo haya un hilo ejecutándose aparte del hilo principal, el problema persiste. También puedo detectar en mi dll, con una función principal (en windows) que los hilos se separan del dll. – user1037171

+0

alternativamente puede establecer setDaemon (True) en un hilo antes de comenzar –

Cuestiones relacionadas