多次执行后Delphi线程挂起

时间:2015-07-21 00:09:25

标签: multithreading delphi indy

我有一个需要通过idhttp将数据发布到一些http主机的多线程应用程序......主机数量各不相同,我把它们放在一个读入TStringList的TXT文件中。但它每天约有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;

4 个答案:

答案 0 :(得分:2)

IMO,你的线程被困在你的MyThread.Execute while循环中。不能保证一旦进入该循环就会退出(因为DoPostData()方法取决于某些外部响应)。这样,我打赌,一个接一个,每个线程都会卡在那里直到很少(或没有)继续工作。

你应该为你的MyThread.Execute()添加一些日志功能,以确保它不会在某处死亡......你还可以在那里添加一个故障安全退出条件(例如if(TriesCount&gt; one zillion times))然后退出)。

另外,我认为更好的设计是让你的线程始终保持运行,只是为它们提供新的工作,而不是创建/销毁线程,即在开始时创建你的100个线程,并且只在最后创建它们。你的程序执行。但它需要对您的代码进行重大更改。

答案 1 :(得分:2)

您不断创建线程而不释放它们。这意味着您的系统会在一段时间后从资源(窗口句柄或内存)中增长。

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

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

仅从主线程调用

MainControl,并且不会从其他线程访问那里使用的数据,因此不需要关键部分。

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

答案 2 :(得分:1)

首先,我会陷阱&amp;记录异常。

其次,这似乎无限地构建了Form1.Memo1。以这种方式运行系统内存时会发生什么?或超过它的容量。 (自从我处理过Delphi之后已经足够长了,我不记得是否存在这方面的限制。当然,如果这是32位代码。)

答案 3 :(得分:0)

乍一看,我建议将http:= TIdHTTP(Nil)添加到TThread.Create事件,将http.Free添加到TThread的Destroy事件。不确定这是否能解决问题。 Windows确实对每个进程的线程有一个操作系统限制(记不清楚但是数字63会浮现在脑海中。你可能想创建一个线程池来缓存你的线程请求。它可能会更加可靠地执行一个“雷鸣般的群体”请求。我怀疑在那些请求中,一些线程可能会异常终止,这可能会减慢速度,泄漏内存等。启用FullDebugMode和LogMemoryLeakDetailsToFile来检查泄漏可能会发现一些问题。检查任务管理器是否有内存运行过程使用的是另一个问题的温暖指示器;内存使用量增长,永不释放。

祝你好运。

RP