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

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

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

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

Создать c-файл, содержащий (на linux/unix удалить __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.so) -shared the_file.c

В окнах, которые мы могли бы скомпилировать с помощью 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.

Надеюсь, что кто-нибудь может помочь!

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

Ответы [ 2 ]

3 голосов
/ 27 июля 2012

Похоже, это не ошибка ctypes или Python.Я могу воспроизвести ту же утечку, утечку с той же скоростью, написав только код C.

Странно, по крайней мере в Ubuntu Linux 64, утечка происходит, если функция Leaker () с переменными __thread скомпилирована как.so и вызывается из программы с помощью dlopen ().Это не происходит при запуске точно такого же кода, но с обеими частями, скомпилированными вместе как обычная программа на Си.

Я подозреваю, что ошибка заключается в некотором взаимодействии между динамически связанными библиотеками и локальным хранилищем потоков.Тем не менее, это выглядит как довольно плохая ошибка (действительно ли она недокументирована?).

1 голос
/ 29 ноября 2011

Я предполагаю, что проблема в том, чтобы не объединяться с потоками.На странице руководства для pthread_join:

Невозможность присоединиться к потоку, который является присоединяемым (то есть тем, который не отсоединен), создает «поток зомби».Избегайте этого, так как каждый поток-зомби потребляет некоторые системные ресурсы, и когда накопилось достаточно потоков-зомби, больше не будет возможности создавать новые потоки (или процессы).

Если вы измените свой циклчтобы собрать объекты потока и использовать .isAlive () и .join () на них в последнем цикле while, я думаю, что он должен позаботиться об утечке памяти.

...