【问题标题】:TStringList and TThread that does not free all of its memory不会释放所有内存的 TStringList 和 TThread
【发布时间】:2012-06-25 02:49:47
【问题描述】:

使用的版本: Delphi 7.

我正在开发一个在虚拟 ListView 上执行简单 for 循环的程序。数据存储在以下记录中:

type TList=record
  Item:Integer;
  SubItem1:String;
  SubItem2:String;
end;

Item 是索引。 SubItem1 操作的状态(成功与否)。 SubItem2 文件的路径。 for 循环加载每个文件,执行一些操作,然后保存它。操作发生在 TStringList 中。每个文件大约 2mb。

现在,如果我在主窗体上进行操作,它可以完美运行。

多线程,存在巨大的内存问题。不知何故, TStringList 似乎没有被完全释放。在 3-4k 个文件之后,我得到一个 EOutofMemory 异常。有时,软件卡在 500-600mb,有时则没有。在任何情况下,TStringList 总是返回一个 EOutofMemory 异常并且不能再加载任何文件。在内存更大的计算机上,获取异常需要更长的时间。

同样的事情发生在其他组件上。例如,如果我使用 Synapse 的 THTTPSend,那么,一段时间后,软件无法创建任何新线程,因为内存消耗太高。它大约是 500-600mb,而最大应该是 100mb。在主窗体上,一切正常。

我想错误是在我这边。也许我对线程的了解不够。我试图释放 Destroy 事件中的所有内容。我尝试了 FreeAndNil 程序。我一次只尝试一个线程。我尝试手动释放线程(没有 FreeOnTerminate...)

运气不好。

所以这里是线程代码。这只是基本的想法;不是所有操作的完整代码。如果我删除 LoadFile 程序,一切正常。根据线程池为每个文件创建一个线程。

unit OperationsFiles;

interface

uses Classes, SysUtils, Windows;

type
 TOperationFile = class(TThread)
 private
  Position : Integer;
  TPath, StatusMessage: String;
  FileStringList: TStringList;
  procedure UpdateStatus;
  procedure LoadFile;
 protected
  procedure Execute; override;
 public
  constructor Create(Path: String; LNumber: Integer);
 end;

implementation

uses Form1;

procedure TOperationFile.LoadFile;
begin
 try
  FileStringList.LoadFromFile(TPath);
  // Operations...
  StatusMessage := 'Success';
 except
  on E : Exception do StatusMessage := E.ClassName;
 end;
end;

constructor TOperationFile.Create(Path : String; LNumber: Integer);
begin
 inherited Create(False);
 TPath := Path;
 Position := LNumber;
 FreeOnTerminate := True;
end;

procedure TOperationFile.UpdateStatus;
begin
 FileList[Position].SubItem1 := StatusMessage;
 Form1.ListView4.UpdateItems(Position,Position);
end;

procedure TOperationFile.Execute;
begin
 FileStringList:= TStringList.Create;
 LoadFile;

 Synchronize(UpdateStatus);

 FileStringList.Free;
end;

end.

可能是什么问题?

我曾一度认为,可能创建了太多线程。如果用户加载 100 万个文件,那么最终将创建 100 万个线程——尽管只有 50 个线程同时被创建和运行

感谢您的意见。

【问题讨论】:

  • @TLama 已经这样做了……根据 FastMM 的说法,除了 Delphi 7 中的内存泄漏之外,没有内存泄漏。
  • @TLama 13 - 20 字节:AnsiString x 1 29 - 36 字节:未知 x 1 45 - 52 字节:TStringList x 2 我相信这些是 Delphi 本身的内存泄漏。如果我错了,请纠正我。
  • 我从来没有经历过 Delphi 本身的任何内存泄漏。
  • Fastmm 可以提供泄漏内存分配的调用堆栈,请确保您安装了完整版本的 Fastmm。除此之外,您没有显示FileList 的填充方式。我怀疑你的线程代码抛出了一个你不处理的异常,绕过了你对TStringList.Free()的调用。添加try/finally 块,或覆盖DoTerminate(),以确保始终调用Free()
  • @Jerry 旧版 Delphis 中的 RTL/VCL 泄露。 Emba 在开始使用 FastMM 时就开始修复这些漏洞。

标签: delphi memory ram tstringlist tthread


【解决方案1】:

您在问题中显示的代码(可能)没有泄漏。

我说可能是因为Execute 期间引发的异常可能导致泄漏。字符串列表的生命周期应由finally 块保护。

FileStringList:= TStringList.Create;
try
  LoadFile;
  Synchronize(UpdateStatus);
finally
  FileStringList.Free;
end;

也就是说,我预计LoadFile 中的异常吞咽意味着您不会泄漏字符串列表。

您说可能创建了数千个线程。每个线程为其堆栈保留内存,默认堆栈大小为 1MB。一旦您保留了数千个 1MB 堆栈,您就可以轻松耗尽或分割地址空间。

过去我曾见过由于随意创建线程而导致的问题。例如,我有一个程序在创建和销毁线程时失败,存在的线程不超过 256 个。这是在具有 4GB 地址空间的 16 核机器上。您可能有 2GB 的可用地址空间。

尽管您声明任何时候存在的线程不超过 50 个,但我不确定您如何确定这一点。尤其重要的是,因为您已将 FreeOnTerminate 设置为 True,从而放弃了对线程生命周期的控制。

我的猜测是您的问题与您创建的线程数有关。每个处理器一个线程就足够了。重复使用你的线程。为小任务创建和销毁线程的成本很高。

如果这还不足以解决您的问题,那么您需要展示管理线程生命周期的代码。

最后,我想知道你将从线程化这个应用程序中获得多少好处。如果它是 IO 绑定的,那么线程版本可能会更慢!

【讨论】:

  • 我也是这么想的,但是看看问题更新只有 50 个线程同时创建和运行
  • @TLama 有些东西没有加起来。 Q 中的代码不会泄漏。创建太多线程是一种吃掉所有虚拟地址空间的简单方法。
  • @DavidHeffernan - 这个程序遇到了什么故障模式? (创建/销毁线程,不超过 256 个?)。我现在正在解决一个类似的问题 - 一个实时系统,它以大约 10 次/秒的速度从某些硬件中采样数据。第 3 方驱动程序(无论出于何种原因)为每次测量创建和销毁一个线程(每次大约需要 10 毫秒)。经过一天左右的连续循环运行 ~ 36000 线程/小时后,它陷入了爬行状态。该计划在其他方面是紧凑和精简的。很好奇你的经历。
  • @J... 我实际上不记得确切的故障模式。当然,其中一个 Win32 API 调用失败,然后波及运行时 Delphi 异常。但我不记得哪个 API 调用失败了。
  • @DavidHeffernan 很公平......认为问它不会有什么坏处。无论如何,它还给了我一些别的东西来调查。
【解决方案2】:

根据所提供的信息,无法重现您的错误。 Remy 和 David 提供的一些提示可能会对您有所帮助。

看你的程序结构,流程可以分为两种经典的解决方案。

将任务委派给不同线程的第一部分是Single-Producer-Multiple-Consumer 问题。 在这里,可以通过创建少量线程,将线程安全的对象队列传递给它们来解决。 然后主线程将任务对象推入队列。消费者线程负责单独的文件检查任务。

将结果传输到主线程的第二部分是Multiple-Producer-Single-Consumer 问题。 如果在初始化时将第二个线程安全对象队列传递给线程,它们可以轻松地将结果放入队列中。 在计时器事件中从主线程中排出结果队列。

【讨论】:

    猜你喜欢
    • 2013-06-05
    • 2014-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-12
    • 2016-10-01
    • 1970-01-01
    相关资源
    最近更新 更多