#python #memory-leaks #ctypes #thread-local-storage
#python #утечки памяти #ctypes #поток-локальное хранилище
Вопрос:
Я использую 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
удалении __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: %dn",tid);
return;}
Скомпилируйте wit MINGW
в Windows / gcc в Linux, как:
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()
при запуске каждого нового потока не помогает.
Некоторое время я думал, что проблема связана с несовместимыми средами выполнения (python, скомпилированный с Visual Studio, моя библиотека с MINGW
). Но проблема также в Ubuntu, но не на сервере win64, даже если библиотека скомпилирована с помощью MINGW
.
Надеюсь, что кто-нибудь может помочь!
Приветствую, Саймон Коккендорф, Национальная служба обследования и кадастра Дании.
Комментарии:
1.пожалуйста, просмотрите известные ошибки python bugs.python.org/issue6627 bugs.python.org/issue3757
2. Не могли бы вы освободить свои переменные с утечкой при закрытии потока в C?
3. чтобы исправить это, попробуйте использовать malloc и free для инициализации и удаления массива
Ответ №1:
Похоже, что это вообще не ошибка ctypes или Python. Я могу воспроизвести ту же утечку с той же скоростью, написав только C-код.
Как ни странно, по крайней мере, в Ubuntu Linux 64, утечка происходит, если функция Leaker() с переменными __thread скомпилирована как .so и вызывается из программы с dlopen(). Этого не происходит при запуске точно такого же кода, но с обеими частями, скомпилированными вместе как обычная программа на C.
Я подозреваю, что ошибка заключается в некотором взаимодействии между динамически связанными библиотеками и локальным хранилищем потоков. Тем не менее, это выглядит как довольно серьезная ошибка (она действительно недокументирована?).
Комментарии:
1. Похоже, что этот поток подтверждает вашу теорию: sourceware.org/ml/libc-help/2011-04/msg00000.html
2. Да — и, похоже, это зависит от операционной системы. Я получаю поведение в Win XP (перенос библиотеки с помощью python или C #, и я предполагаю, что вы заметили из C) (32-разрядная версия), в Ubuntu (как 32-разрядная, так и 64-разрядная, я полагаю) — однако на сервере Windows (64-разрядная версия) я этого не вижу.
Ответ №2:
Я предполагаю, что проблема заключается в том, что не соединение с потоками является проблемой. Со страницы руководства для pthread_join:
Сбой при соединении с потоком, который можно объединить (то есть с тем, который не отсоединен), создает «поток зомби». Избегайте этого, поскольку каждый зомби-поток потребляет некоторые системные ресурсы, и когда накопится достаточное количество зомби-потоков, больше не будет возможности создавать новые потоки (или процессы).
Если вы измените свой цикл, чтобы собирать объекты потока и использовать.IsAlive() и .join () для них в этом последнем цикле while, я думаю, это должно позаботиться о вашей утечке памяти.
Комментарии:
1. Спасибо за ваш ответ. Однако, мне не кажется, что проблема не в присоединении к потокам. Даже если я присоединюсь к каждому потоку сразу после создания, так что будет только один поток, работающий отдельно от основного потока, проблема останется. Я также могу обнаружить в своей dll с помощью функции main (под Windows), что потоки отсоединяются от dll.
2. в качестве альтернативы вы можете установить setDaemon (True) в потоке перед запуском