我在delphi中遇到多线程问题。我有一个名单(大约有2000个名字),我需要在我的网站上获取每个名字的一些数据。除了线程控制之外,我的系统工作正常。
我想创建10个线程,并且当某个线程终止时,创建另一个...直到列表的结尾。
var
Form1: TForm;
tCount: Integer; //threads count
implementation
type
TCheck = class(TThread)
public
constructor Create(Name: string);
destructor Destroy; Override;
protected
procedure Execute; Override;
end;
MainT = class(TThread)
protected
procedure Execute; Override;
end;
destructor TCheck.Destroy;
begin
Dec(tCount);
end;
procedure MainT.Execute;
var
i: Integer;
Load: TStringList;
begin
Load:=TStringList.Create;
Load.LoadFromFile('C:\mynames.txt');
for i:= 0 to Load.Count -1 do
begin
if tCount = 10 then //if we have 10 threads running...
begin
repeat
Sleep(1);
until tCount < 10;
end;
TCheck.Create(Load.Strings[i]);
TCheck.Start;
Inc(tCount);
end;
end; // end of procedure
好吧,我没有放TCheck.Constructor,因为问题是我如何检查创建的线程数。我的意思是,我的软件只是停止,没有任何错误信息,有时检查500个名字,有时150个名字......
抱歉英语不好。
答案 0 :(得分:3)
这是一个使用泛型的线程安全队列解决方案。
定义所需的消费者线程数,队列深度,然后从线程中运行DoSomeJob
过程。
使用字符串作为通用程序(在CaptureJob
中)定义您的工作。
当队列为空时,将销毁使用者线程。 DoSomeJob
过程等待所有作业都准备就绪。
您可以轻松地将其转换为通用工作池,重用线程而不会破坏它们。作业项的通用结构也使它们适合处理不同类型的工作。
请注意,此队列适用于XE2及更高版本。如果您使用的是较旧的delphi版本,请查看注释中建议的类似线程安全队列。
uses
Classes,SyncObjs,Generics.Collections;
Type
TMyConsumerItem = class(TThread)
private
FQueue : TThreadedQueue<TProc>;
FSignal : TCountDownEvent;
protected
procedure Execute; override;
public
constructor Create( aQueue : TThreadedQueue<TProc>; aSignal : TCountdownEvent);
end;
constructor TMyConsumerItem.Create(aQueue: TThreadedQueue<TProc>);
begin
Inherited Create(false);
Self.FreeOnTerminate := true;
FQueue := aQueue;
FSignal := aSignal;
end;
procedure TMyConsumerItem.Execute;
var
aProc : TProc;
begin
try
repeat
FQueue.PopItem(aProc);
if not Assigned(aProc) then
break; // Drop this thread
aProc();
until Terminated;
finally
FSignal.Signal;
end;
end;
procedure DoSomeJob(myListItems : TStringList);
const
cThreadCount = 10;
cMyQueueDepth = 100;
var
i : Integer;
aQueue : TThreadedQueue<TProc>;
aCounter : TCountDownEvent;
function CaptureJob( const aString : string) : TProc;
begin
Result :=
procedure
begin
// Do some job with aString
end;
end;
begin
aQueue := TThreadedQueue<TProc>.Create(cMyQueueDepth);
aCounter := TCountDownEvent.Create(cThreadCount);
try
for i := 1 to cThreadCount do
TMyConsumerItem.Create(aQueue,aCounter);
for i := 0 to myListItems.Count-1 do begin
aQueue.PushItem( CaptureJob( myListItems[i]));
end;
finally
for i := 1 to cThreadCount do
aQueue.PushItem(nil);
aCounter.WaitFor; // Wait for threads to finish
aCounter.Free;
aQueue.Free;
end;
end;
NB :Ken解释了为什么初始化和线程启动错误。该提案显示了一种更好的结构,以更通用的方式处理这类问题。
答案 1 :(得分:1)
如果您未声明变量以保留TCheck.Create
的返回值,则无法访问TCheck.Start
(您无法使用TCheck
的{{1}}实例访问{ {1}}方法)。
正确的方法是在Start
内声明var Check: TCheck;
,然后存储返回的值:
MainT.Execute
注意 Check := TCheck.Create(Load[i]); { See note below }
Check.Start;
Inc(tCount);
的默认属性为TStringList
,因此您无需使用它。您可以直接访问Strings
,如上所述。接下来的两行是完全相同的(但显然一个更短更容易输入):
Strings
如果您不想保留对Load.Strings[i];
Load[i];
的引用,只需将您的代码更改为TCheck
块(包括with
,并且该块中不包含其他代码(这是唯一的方式我建议使用begin..end
):
with
话虽如此,有更好的方法可以做到这一点,而不是创建/销毁各种线程。正如其他人所说,你可以拥有一个包含10个线程的列表并为它们排队工作,这样每个线程都会处理来自with TCheck.Create(Load[i]) do
begin
Start;
Inc(tCount);
end;
的项目然后返回以完成另一个要处理的项目,并重复直到列表完成了。很难确切地说你将如何做到这一点,因为这取决于你的Delphi版本。 (有些库可以为你完成大部分工作,比如OMNIThreadLibrary
,但它不适用于某些旧版本的Delphi。最新版本的Delphi也支持Load
和TQueue
以及一些可能非常有用的其他类型和功能。
(如果您对如何在线程数量有限的队列中执行此操作有不同的问题,那应该是新问题,而不是您添加到此问题的内容。)