Утечка памяти при использовании разделяемой библиотеки с локальным хранилищем потока через ctypes в программе на Python

Я использую модуль ctypes в python для загрузки разделяемой c-библиотеки, которая содержит локальное хранилище потоков. Это довольно большая c-библиотека с долгой историей, которую мы пытаемся сделать потокобезопасной. Библиотека содержит множество глобальных переменных и статики, поэтому наша первоначальная стратегия обеспечения безопасности потоков заключалась в использовании локального хранилища потоков. Мы хотим, чтобы наша библиотека была независимой от платформы, и мы компилируем и тестируем потокобезопасность как на win32, так и на win64, так и на 64-битной Ubuntu. С чистым c-процессом проблем не возникает.

Однако в python (2.6 и 2.7) на win32 и Ubuntu мы наблюдаем утечки памяти. Похоже, что локальное хранилище потока не освобождается должным образом, когда поток Python завершается. Или, по крайней мере, каким-то образом процесс Python "не знает" об освобождении памяти. Та же проблема наблюдается и в C # -программе на win32, но ее нет на нашей тестовой машине с сервером win64 (на которой также работает python 2.7).

Проблема может быть воспроизведена на простом игрушечном примере вроде этого:

Создайте c-файл, содержащий (на linux / unix remove __ 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;}

Скомпилируйте остроумие MINGW на windows / gcc на linux, например:

gcc -o leaky.dll (или leaky.так ) -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 () при запуске каждого нового потока, это не помогает.

Некоторое время я думал, что проблема связана с несовместимыми средами выполнения (python, скомпилированный с Visual Studio, моя библиотека с MINGW ). Но проблема также существует в Ubuntu, но не на сервере win64, даже когда библиотека кросс-компилируется с MINGW .

Надеюсь, что любой может помочь!

Ура, Саймон Коккендорф, Национальное управление и кадастр Дании.

12
задан Ivan Kharlamov 10 November 2011 в 13:11
поделиться