2011-11-10 8 views
12

Sto utilizzando il modulo ctypes in python per caricare una libreria c condivisa, che contiene l'archiviazione locale dei thread. È una libreria c piuttosto grande con una lunga storia, che stiamo cercando di rendere sicuri i thread. La libreria contiene molte variabili e statiche globali, quindi la nostra strategia iniziale per la sicurezza dei thread è stata quella di utilizzare l'archiviazione locale dei thread. Vogliamo che la nostra libarary sia indipendente dalla piattaforma e che stiamo compilando e testando la sicurezza dei thread sia su Ubuntu win32, win64 e 64-bit. Da un puro processo c non sembrano esserci problemi.Perdita di memoria quando si utilizza la libreria condivisa con archiviazione locale dei thread tramite ctypes in un programma python

Tuttavia in python (2.6 e 2.7) su win32 e su Ubuntu stiamo assistendo a perdite di memoria. Sembra che la memoria locale del thread non venga rilasciata correttamente quando un thread python termina. O almeno che in qualche modo il processo Python non è "consapevole" che la memoria viene liberata. Lo stesso problema si vede anche in un programma in C# su win32 in realtà, ma non è presente sulla nostra macchina per test server Win64 (anche in esecuzione python 2.7).

Il problema può essere riprodotto con un semplice esempio giocattolo come questo:

Creare un c-file contenente (su linux/unix rimuovere __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;} 

Compila spirito MINGW su Windows/gcc su linux come:

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

Su Windows abbiamo potuto compilare con Visual Studio, sostituendo __thread con __declspec(thread). Tuttavia su win32 (fino a winXP credo), questo non funziona se la libreria deve essere caricata in runtime con LoadLibrary.

Ora creare un programma di pitone come:

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

Questo è sufficiente per riprodurre l'errore. Importare esplicitamente il garbage collector, facendo un collect gc.collect() all'avvio di ogni nuovo thread non aiuta.

Per un po 'ho pensato che il problema avesse a che fare con runtime incompatibili (python compilato con Visual Studio, la mia libreria con MINGW). Ma il problema è anche su Ubuntu, ma non su un server Win64, anche quando la biblioteca è croce compilato con MINGW.

speranza che chiunque può aiutare!

Cheers, Simon Kokkendorff, National Survey e Catasto di Danimarca.

+0

consulta noti bug di pitone http://bugs.python.org/issue6627 http://bugs.python.org/issue3757 –

+0

Potrebbe liberare le variabili che perde sul filo vicino in C? –

+0

per risolvere questo problema, prova ad usare malloc e gratis per inizializzare e rimuovere l'array – pyCthon

risposta

3

Questo sembra non essere affatto colpa di Ctypes o Python. Posso riprodurre la stessa perdita, perdendo alla stessa velocità, scrivendo solo il codice C.

Stranamente, almeno su Ubuntu Linux 64, la perdita si verifica se la funzione Leaker() con le variabili __thread è compilata come un .so e chiamata da un programma con dlopen(). Non si verifica quando si esegue esattamente lo stesso codice ma con entrambe le parti compilate insieme come un normale programma C.

Ho il sospetto che l'errore sia un'interazione tra le librerie collegate dinamicamente e l'archiviazione locale dei thread. Tuttavia, sembra un bug piuttosto brutto (è davvero non documentato?).

+0

Sembra che questo thread esegua il backup della teoria: http://sourceware.org/ml/libc-help/2011-04/msg00000.html –

+0

Sì - e sembra essere dipendente dal sistema operativo. Ottengo il comportamento su Win XP (avvolgendo la libreria con Python o C#, e immagino come si nota da C) (32 bit), su Ubuntu (sia a 32 bit che a 64 bit, credo) - comunque su un server Windows (64 bit), non lo vedo. – user1037171

1

La mia ipotesi è che non unirsi con i fili è il problema. Dalla pagina man per pthread_join:

mancata adesione con un filo che è joinable (cioè, uno che non è staccato), produce un "filo zombie". Evitare di fare questo, dal momento che ogni filo zombie consuma alcune risorse di sistema, e quando abbastanza zombie discussioni hanno accumulato, non sarà più possibile creare nuovi discussioni (o processi).

Se si modifica il loop per raccogliere gli oggetti thread e si utilizzano .isAlive() e .join() su di essi in quest'ultimo ciclo while penso che si dovrebbe occuparsi della perdita di memoria.

+0

Grazie per la tua risposta. Non sembra che non unire i thread sia il problema. Anche se unisco ogni thread subito dopo la creazione, in modo che ci sia solo un thread che si allontana dal thread principale, il problema rimane. Posso anche rilevare nella mia DLL, con una funzione principale (sotto Windows) che i thread si staccano dalla DLL. – user1037171

+0

in alternativa è possibile impostare setDaemon (True) su una discussione prima di iniziare –

Problemi correlati