我有一个需要通过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;
答案 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