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