2012-01-27 6 views
13

Postanowiłem nigdy nie polegać wyłącznie na ustawianiu właściwości wątku na FreeOnTerminate, aby być pewnym jej zniszczenia, ponieważ odkryłem i uzasadniłem dwie rzeczy na zakończenie aplikacji:Thread.FreeOnTerminate: = Prawda, przeciek pamięci i upiory

  1. produkuje przeciek pamięci, a
  2. po zakończeniu programu, nitka wciąż działa gdzieś poniżej klawiaturze moim notebooku.

Zapoznałem się z obejściem problemu i nie przeszkadzało mi to przez cały ten czas. Aż do dzisiaj, kiedy znowu ktoś (@MartinJames w tym przypadku) skomentował na my answer, w którym odwołuję się do kodu, który nie używa FreeOnTerminate w połączeniu z przedwczesnym zakończeniem wątku. Wróciłem do kodu RTL i zdałem sobie sprawę, że mogłem popełnić złe założenia. Ale nie jestem do końca tego pewien, stąd to pytanie.

Po pierwsze, aby odtworzyć powyższe wypowiedzi, używany jest ten kod ilustracyjne:

unit Unit3; 

interface 

uses 
    Classes, Windows, Messages, Forms; 

type 
    TMyThread = class(TThread) 
    FForm: TForm; 
    procedure Progress; 
    procedure Execute; override; 
    end; 

    TMainForm = class(TForm) 
    procedure FormClick(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    private 
    FThread: TMyThread; 
    end; 

implementation 

{$R *.dfm} 

{ TMyThread } 

procedure TMyThread.Execute; 
begin 
    while not Terminated do 
    begin 
    Synchronize(Progress); 
    Sleep(2000); 
    end; 
end; 

procedure TMyThread.Progress; 
begin 
    FForm.Caption := FForm.Caption + '.'; 
end; 

{ TMainForm } 

procedure TMainForm.FormClick(Sender: TObject); 
begin 
    FThread := TMyThread.Create(True); 
    FThread.FForm := Self; 
    FThread.FreeOnTerminate := True; 
    FThread.Resume; 
end; 

procedure TMainForm.FormDestroy(Sender: TObject); 
begin 
    FThread.Terminate; 
end; 

end. 

Now (sytuacja A), jeśli zaczniesz nić za pomocą kliknięcia na formularzu, a następnie zamknij formularz tuż po podpis zmienił się, istnieje wyciek pamięci o długości 68 bajtów. Zakładam, że dzieje się tak, ponieważ wątek nie został uwolniony. Po drugie, program kończy się natychmiast, a IDE jest w tej samej chwili z powrotem w normalnym stanie. W przeciwieństwie do (sytuacja B): gdy nie używasz FreeOnTerminate, a ostatnia linia powyższego kodu zmienia się na FThread.Free, trwa (maksymalnie) 2 sekundy od zniknięcia programu do normalnego stanu IDE.

Opóźnienie w sytuacji B tłumaczy się tym, że FThread.Free dzwoni FThread.WaitFor, oba są wykonywane w kontekście głównego wątku. Dalsze badania Classes.pas dowiodły, że zniszczenie wątku z powodu FreeOnTerminate odbywa się w kontekście wątku roboczego. To prowadzi do następujących pytań na temat sytuacji:

  • Czy rzeczywiście występuje wyciek pamięci? A jeśli tak, to czy jest to ważne, czy można go zignorować? Ponieważ po zamknięciu aplikacji system Windows nie zwraca wszystkich zarezerwowanych zasobów?
  • Co dzieje się z nitką? Czy rzeczywiście biegnie gdzieś w pamięci, dopóki jej praca nie zostanie zakończona, czy nie? I: czy jest uwolniony, pomimo dowodów wycieku pamięci?

Nota prawna: W przypadku wykrywania nieszczelności pamięci używam this very simple unit jako pierwszego w pliku projektu.

+0

Nie wiem, czy już czytałeś, ale uważam, że bardzo przydatne są posty z [Raymond Chen] (http: //blogs.msdn.com/b/oldnewthing) na temat tego rodzaju tematów. Weź to [jeden jako przykład] (http://blogs.msdn.com/b/oldnewthing/archive/2012/01/05/10253268.aspx) lub [dwa] (http://blogs.msdn.com/ b/oldnewthing/archive/2007/05/02/2365433.aspx # 2375204). Zobacz także komentarze i połączone posty. – EMBarbosa

Odpowiedz

12

Rzeczywiście, system operacyjny odzyskuje całą pamięć procesu po jej zakończeniu, więc nawet jeśli te 68 bajtów odnosi się do niezwiązanego obiektu wątku, system operacyjny i tak odbierze te bajty. Nie ma znaczenia, czy uwolniłeś obiekt w tym momencie.

Po zakończeniu głównego programu osiąga on w końcu miejsce, w którym wywołuje ExitProcess. (Powinieneś być w stanie włączyć debugujące jednostki DCU w opcjach linkera projektu i przejść do tego punktu za pomocą debuggera.) To wywołanie API wykonuje kilka rzeczy, w tym kończy wszystkie inne wątki. Wątki nie są powiadamiane o ich zakończeniu, więc kod czyszczenia dostarczony przez TThread nigdy nie działa. Wątek systemu operacyjnego po prostu przestaje istnieć.

+0

+1 System operacyjny zatrzymuje wszystkie wątki procesowe, zanim zwolni pamięć procesową - dlatego nie otrzymujesz żadnych AV i jest to całkiem bezpieczne, jeśli chodzi o pamięć, aby pozwolić systemowi na czyszczenie. System operacyjny może zatrzymać "natychmiast" dowolny wątek w dowolnym stanie, nawet jeśli działa on na innym rdzeniu niż wątek wywołujący ExitProcess(), a więc twoja aplikacja wyłącza się bezzwłocznie. Oczywiście są takie przypadki, w których trzeba wyraźnie powstrzymać wątki - być może transakcja wymaga zatwierdzenia lub spłukany plik. W przeciwnym razie ostrzeżenie o wycieku jest fałszywe - nie przejmuj się, jeśli zawsze jest i nigdy nie rośnie. –

+0

Jeszcze jeden punkt - podczas zamykania można uzyskać skrzynki wyjątków AV/s lub 216/217, ale trzeba spróbować. Jednym ze sposobów na to, aby wątki były czytane/zapisywane bezpośrednio na polach formularzy - RTL zamyka formularze i zwalnia pamięć formularzy przed osiągnięciem ExitProcess(), więc taki wątek może próbować uzyskać dostęp do zwolnionej pamięci podczas zamykania. Po prostu nie robię takich rzeczy i nie mam żadnych problemów. –