【问题标题】:Delphi threads hanging after many executionsDelphi线程在多次执行后挂起
【发布时间】:2015-07-21 00:09:24
【问题描述】:

我有一个多线程应用程序需要通过 idhttp 将数据发布到一些 http 主机...主机的数量各不相同,我将它们放在一个 TXT 文件中,该文件被读入 TStringList。但每天大约有 5k 主机。好的,运行 3 天后,或多或少,检查了大约 15k 个主机,线程在代码的某个点开始挂起,程序变得非常慢,就像它开始每 10 分钟检查 1 个主机......有时它会运行远,并保持 1 周运行得非常好,但在同样的问题之后:看起来大多数线程开始挂起......我不知道问题到底出在哪里,因为我用 100 个线程运行它,就像我说的,在 15k 或更多主机之后,它开始变慢...

这是几乎完整的源代码(很抱歉发布完整,但我认为它比更少更好)

type
  MyThread = class(TThread)
  strict private
    URL, FormPostData1, FormPostData2: String;
    iData1, iData2: integer;
    procedure TerminateProc(Sender: TObject);
    procedure AddPosted;
    procedure AddStatus;
    function PickAData: bool;
    function CheckHost: bool;
    function DoPostData(const FormPostData1: string; const FormPostData2: string): bool;
  protected
    constructor Create(const HostLine: string);
    procedure Execute; override;
  end;

var
  Form1: TForm1;
  HostsFile, Data1, Data2: TStringList;
  iHost, iThreads, iPanels: integer;
  MyCritical: TCriticalSection;

implementation

function MyThread.CheckHost: bool;
var
  http: TIdHTTP;
  code: string;
begin
  Result:= false;
  http:= TIdHTTP.Create(Nil);
  http.IOHandler:= TIdSSLIOHandlerSocketOpenSSL.Create(http);
  http.Request.UserAgent:= 'Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
  http.HandleRedirects:= True;
  try
    try
      code:= http.Get(URL);
      if(POS('T2ServersForm', code) <> 0) then
        Result:= true;
    except
      Result:= false;
    end;
  finally
    http.Free;
  end;
end;

function MyThread.PickAData: bool;
begin
  Result:= false;
  if (iData2 = Data2.Count) then
    begin
      inc(iData1);
      iData2:= 0;
    end;
  if iData1 < Data1.Count then
    begin
      if iData2 < Data2.Count then
        begin
          FormPostData2:= Data2.Strings[iData2];
          inc(iData2);
        end;
      FormPostData1:= Data1.Strings[iData1];
      Result:= true;
    end;
end;

function MyThread.DoPostData(const FormPostData1: string; const FormPostData2: string): bool;
var
  http: TIdHTTP;
  params: TStringList;
  response: string;
begin
  Result:= false;
  http:= TIdHTTP.Create(Nil);
  http.Request.UserAgent := 'Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
  http.Request.ContentType := 'application/x-www-form-urlencoded';
  params:= TStringList.Create;
  try
    params.Add('LoginType=Explicit');
    params.Add('Medium='+FormPostData1);
    params.Add('High='+FormPostData2);
    try
      response:= http.Post(Copy(URL, 1, POS('?', URL) - 1), params);
      if http.ResponseCode = 200 then
        Result:= true;
    except
      if (http.ResponseCode = 302) then
        begin
          if(POS('Invalid', http.Response.RawHeaders.Values['Location']) = 0) then
            Result:= true;
        end
      else
        Result:= true;
    end;
  finally
    http.Free;
    params.Free;
  end;
end;

procedure MyThread.AddPosted;
begin
  Form1.Memo1.Lines.Add('POSTED: ' + URL + ':' + FormPostData1 + ':' + FormPostData2)
end;

procedure MyThread.AddStatus;
begin
  inc(iPanels);
  Form1.StatusBar1.Panels[1].Text:= 'Hosts Panels: ' + IntToStr(iPanels);
end;

procedure MainControl;
var
  HostLine: string;
begin
  try
    MyCritical.Acquire;
    dec(iThreads);
    while(iHost <= HostsFile.Count - 1) and (iThreads < 100) do
    begin
      HostLine:= HostsFile.Strings[iHost];
      inc(iThreads);
      inc(iHost);
      MyThread.Create(HostLine);
    end;
    Form1.StatusBar1.Panels[0].Text:= 'Hosts Checked: ' + IntToStr(iHost);
    if(iHost = HostsFile.Count - 1) then
    begin
      Form1.Memo1.Lines.Add(#13#10'--------------------------------------------');
      Form1.Memo1.Lines.Add('Finished!!');
    end;
  finally
    MyCritical.Release;
  end;
end;

{$R *.dfm}

constructor MyThread.Create(const HostLine: string);
begin
  inherited Create(false);
  OnTerminate:= TerminateProc;
  URL:= 'http://' + HostLine + '/ServLan/Controller.php?action=WAIT_FOR';
  iData2:= 0;
  iData1:= 0;
end;

procedure MyThread.Execute;
begin
  if(CheckHost = true) then
  begin
    Synchronize(AddStatus);
    while not Terminated and PickAData do
    begin
      try
        if(DoPostData(FormPostData1, FormPostData2)  = true) then
          begin
            iData1:= Data1.Count;
            Synchronize(AddPosted);
          end;
      except
        Terminate;
      end;
    end;
    Terminate;
  end;
end;

procedure MyThread.TerminateProc(Sender: TObject);
begin
  MainControl;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  if (FileExists('data2.txt') = false) OR (FileExists('data1.txt') = false) then
    begin
      Button1.Enabled:= false;
      Memo1.Lines.Add('data2.txt / data1.txt not found!!');
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  openDialog : TOpenDialog;
begin
  try
    HostsFile:= TStringList.Create;
    openDialog := TOpenDialog.Create(Nil);
    openDialog.InitialDir := GetCurrentDir;
    openDialog.Options := [ofFileMustExist];
    openDialog.Filter := 'Text File|*.txt';
    if openDialog.Execute then
    begin
     HostsFile.LoadFromFile(openDialog.FileName);
     Button2.Enabled:= true;
     Button1.Enabled:= false;
    end;
  finally
    openDialog.Free;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Button2.Enabled:= false;
  Data1:= TStringList.Create;
  Data1.LoadFromFile('data1.txt');
  Data2:= TStringList.Create;
  Data2.LoadFromFile('data2.txt');
  MyCritical:= TCriticalSection.Create;
  iHost:= 0;
  iThreads:= 0;
  MainControl;
end;

【问题讨论】:

    标签: multithreading delphi indy


    【解决方案1】:

    你不断地创建线程而不释放它们。这意味着您的系统将在一段时间后耗尽资源(窗口句柄或内存)。

    在线程构造函数中设置FreeOnTerminate := true,以在终止时释放线程。

    如果您在调试模式下启动程序时声明了ReportMemoryLeaksOnShutdown := true,则会报告此泄漏。


    MainControl 仅在主线程中调用,使用的数据不从其他线程访问,因此不需要临界区。

    使用线程池还有助于提高应用程序的响应速度。

    【讨论】:

      【解决方案2】:

      IMO,您的线程被困在 MyThread.Execute while 循环中。无法保证一旦进入该循环,它将退出(因为 DoPostData() 方法取决于某些外部响应)。这样一来,我敢打赌,每个线程都会被卡在其中,直到很少(或没有)仍然工作。

      您应该在 MyThread.Execute() 中添加一些日志功能,以确保它不会在某处死亡...您还可以在此处添加故障安全退出条件(例如,如果 (TriesCount > one zillion times) then退出)。

      另外,我考虑了一个更好的设计来让你的线程一直运行并且只是为它们提供新的工作,而不是创建/销毁线程,即在开始时创建你的 100 个线程并且只在结束时销毁它们你的程序执行。但这需要对您的代码进行重大更改。

      【讨论】:

      • 这就是我认为正在发生的事情,它们被卡在执行的某个地方,一个接一个,直到没有工作......我认为是在 CheckHost() 或 DoPostData() 上,因为两者使用 IdHTTP。但是我尝试使用 try/finally 来缓解任何问题 - try/except... 我不知道在这种情况下还可以做些什么来始终退出此功能。你脑子里有什么伪代码吗?关于新设计,随着时间的推移,我正在使这个实现变得更好,一开始我总是遇到访问冲突,今天我已经解决了这个问题。但是我对这门语言的了解仍然不足以完成您建议我的设计。
      • 我猜你应该按照上面的建议从 FreeOnTerminate := true 开始,看看你会得到什么。
      【解决方案3】:

      首先,我会捕获并记录异常。

      其次,这似乎无限构建 Form1.Memo1。当您以这种方式运行系统内存不足时会发生什么?或者超过它的能力。 (处理Delphi已经很久了,不记得有没有这方面的限制。如果是32位代码肯定有。)

      【讨论】:

      • 嗯...我想我只是在 Memo1 中添加行。我不构建它,我只是使用 Add.Lines 方法...
      • @user3810691 是的,您正在添加行——它们可能是无限的。这些线条占用空间!
      【解决方案4】:

      乍一看,我建议将 http := TIdHTTP(Nil) 添加到 TThread.Create 事件,并将 http.Free 添加到 TThread 的 Destroy 事件。不确定这是否能解决问题。 Windows 确实对每个进程的线程有操作系统限制(记不太清了,但我想到了 63 号。您可能想要创建一个线程池来缓存您的线程请求。它可能会在“雷鸣般的群”中更可靠地执行请求。我怀疑在这个数量的请求中,一些线程可能会异常终止,这可能会减慢速度,泄漏内存等。启用 FullDebugMode 和 LogMemoryLeakDetailsToFile 来检查泄漏可能会揭示一些东西。检查任务管理器以观察内存正在运行的进程所使用的内存是另一个不冷不热的问题指标;内存使用量会增长,但永远不会释放。

      祝你好运。

      RP

      【讨论】:

        猜你喜欢
        • 2019-05-08
        • 2014-06-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多