Thread.FreeOnTerminate: = True, утечка памяти и работа призрака - PullRequest
13 голосов
/ 27 января 2012

Несколько лет назад я решил никогда не полагаться исключительно на установку свойства FreeOnTerminate потока в значение true, чтобы быть уверенным в его разрушении, потому что я обнаружил и обосновал две вещи при завершении приложения:

  1. выдает утечку памяти, а
  2. после завершения программы поток все еще работает где-то под клавиатурой моего ноутбука.

Я ознакомился с обходным путем, и это не беспокоило меня все это время. До сегодняшнего вечера, когда снова кто-то (@MartinJames в данном случае) прокомментировал мой ответ , в котором я ссылаюсь на некоторый код, который не использует FreeOnTerminate в сочетании с преждевременным завершением потока. Я вернулся в код RTL и понял, что, возможно, сделал неверные предположения. Но я не совсем уверен в этом, отсюда и этот вопрос.

Во-первых, для воспроизведения вышеупомянутых утверждений используется этот иллюстративный код:

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.

Теперь (ситуация A), если вы запускаете поток нажатием на форму и закрываете форму сразу после изменения заголовка, происходит утечка памяти в 68 байт. Я предполагаю, что это потому, что поток не освобожден. Во-вторых, программа немедленно завершается, и в этот момент IDE снова возвращается в нормальное состояние. Это в отличие от (ситуация B): когда не используется FreeOnTerminate, а последняя строка вышеуказанного кода изменяется на FThread.Free, от исчезновения программы до нормальной IDE требуется (максимум) 2 секунды состояние.

Задержка в ситуации B объясняется тем, что FThread.Free вызывает FThread.WaitFor, оба из которых выполняются в контексте основного потока. Дальнейшее изучение Classes.pas показало, что уничтожение потока из-за FreeOnTerminate выполняется в контексте рабочего потока. Это приводит к следующим вопросам о ситуации A:

  • Действительно ли есть утечка памяти? И если так: важно ли это, можно ли его игнорировать? Потому что, когда приложение завершает работу, Windows не возвращает все свои зарезервированные ресурсы?
  • Что происходит с потоком? Действительно ли он движется дальше где-то в памяти, пока его работа не будет выполнена, или нет? И: освобожден ли он, несмотря на признаки утечки памяти?

Отказ от ответственности: для обнаружения утечки памяти я использую это очень простое устройство в качестве первого в файле проекта.

1 Ответ

12 голосов
/ 27 января 2012

Действительно, операционная система освобождает всю память процесса, когда он завершается, поэтому, даже если эти 68 байтов ссылаются на объект освобождаемого потока, ОС все равно вернет эти байты обратно. Неважно, освободили ли вы объект в этот момент.

Когда ваша основная программа заканчивается, она в конечном итоге достигает места, где она вызывает ExitProcess. (Вы должны иметь возможность включить отладочные DCU в параметрах компоновщика вашего проекта и перейти к этому моменту с помощью отладчика.) Этот вызов API выполняет несколько вещей, включая завершение всех других потоков. Потоки не уведомляются о том, что они завершаются, поэтому код очистки, предоставленный TThread, никогда не запускается. Поток ОС просто перестает существовать.

...