Я использую модуль 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
.
Надеюсь, что кто-нибудь может помочь!
Приветствия, Саймон Коккендорф, Национальное управление и кадастр Дании.