【问题标题】:Thread.FreeOnTerminate := True, memory leak and ghost runningThread.FreeOnTerminate := True,内存泄漏和幽灵运行
【发布时间】:2012-02-20 05:33:51
【问题描述】:

多年前,我决定永远不要仅仅依靠将线程的 FreeOnTerminate 属性设置为 true 来确保它的销毁,因为我在应用程序终止时发现并推理了两件事:

  1. 它会产生内存泄漏,并且
  2. 程序终止后,线程仍在我笔记本键盘下方的某处运行。

我熟悉了一种解决方法,并且一直没有困扰我。直到今晚,当再次有人(在这种情况下为@MartinJames)评论my answer 时,我提到了一些不使用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 不会归还其所有保留的资源吗?
  • 线程会发生什么?它是否确实在内存中的某个地方运行得更远,直到它的工作完成,或者没有?并且:尽管有内存泄漏的证据,它是否被释放?

免责声明:对于内存泄漏检测,我在项目文件中首先使用this very simple unit

【问题讨论】:

  • 我不知道您是否已经阅读过,但我确实发现来自Raymond Chen 的关于此类主题的帖子非常有用。拿这个one as an exampletwo。也看看 cmets 和链接的帖子。

标签: multithreading delphi memory-leaks delphi-7


【解决方案1】:

确实,操作系统在进程终止时会回收所有进程的内存,因此即使这 68 个字节引用了未释放的线程对象,操作系统也会取回这些字节。那时你是否释放了对象并不重要。

当您的主程序完成时,它最终会到达一个调用ExitProcess 的地方。 (您应该能够在项目的链接器选项中打开调试 DCU,并使用调试器单步执行该点。)该 API 调用做了几件事,包括终止所有其他线程。线程不会被通知它们正在终止,因此TThread 提供的清理代码永远不会运行。操作系统线程根本不存在。

【讨论】:

  • +1 操作系统在释放进程内存之前停止所有进程线程——这就是为什么你没有获得 AV 的原因,而且从内存方面来说,让操作系统清理是非常安全的。操作系统可以“立即”停止处于任何状态的任何线程,即使它在另一个内核上而不是调用 ExitProcess() 的线程上运行,因此您的应用程序会立即关闭。自然,在某些情况下,您确实需要尝试显式停止线程——可能需要提交事务或刷新文件。否则,泄漏警告是虚假的 - 如果它始终存在并且永远不会增长,请不要担心。
  • 最后一点 - 您可以在关机时获得 AV/s 或 216/217 异常框,但您必须尝试。一种方法是让线程直接读取/写入表单字段 - RTL 在到达 ExitProcess() 之前关闭表单并释放表单内存,因此这样的线程可能会在关闭期间尝试访问释放的内存。我只是不做这样的事情,所以我没有问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-02
  • 2013-11-08
  • 2016-05-03
  • 2011-11-25
相关资源
最近更新 更多