Я использую модуль ctypes
в python для загрузки общей c-библиотеки, которая содержит локальное хранилище потоков. Это довольно большая c-библиотека с длинной историей, которую мы пытаемся сделать потокобезопасной. Библиотека содержит множество глобальных переменных и статики, поэтому наша первоначальная стратегия по обеспечению безопасности потоков заключалась в использовании локального хранилища потоков. Мы хотим, чтобы наш libarary был независимым от платформы, а также собирал и тестировал безопасность потоков как на win32, win64, так и на 64-битных Ubuntu. Из чистого c-процесса проблем не возникает.
Однако в python (2.6 и 2.7) на win32 и на Ubuntu мы видим утечки памяти. Кажется, что локальное хранилище потоков не освобождается должным образом, когда поток python завершается. Или, по крайней мере, так или иначе процесс python не "осознает", что память освобождается. Та же проблема также встречается в С# -программе на 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: %d\n",tid);
return;}
Скомпилируйте wit 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()
при запуске каждого нового потока не помогает.
Некоторое время я думал, что проблема связана с несовместимыми runtimes (python скомпилирован с Visual Studio, моя библиотека с MINGW
). Но проблема также в Ubuntu, но не на сервере win64, даже когда библиотека скроена с помощью MINGW
.
Надеюсь, что любой может помочь!
Cheers, Саймон Кокендорф, Национальный опрос и кадастр Дании.