多线程Delphi

时间:2013-03-25 18:37:39

标签: multithreading delphi

我在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个名字......

抱歉英语不好。

2 个答案:

答案 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也支持LoadTQueue以及一些可能非常有用的其他类型和功能。

(如果您对如何在线程数量有限的队列中执行此操作有不同的问题,那应该是新问题,而不是您添加到此问题的内容。)