6

我在delphi中遇到多线程问题。我有一个名称列表(大约 2.000 个名称),我需要获取我网站中每个名称的一些数据。我的系统运行良好,除了线程控制。

我想创建 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 个名称......

抱歉英语不好。

4

2 回答 2

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;

注意:Ken 解释了为什么你的初始化和线程启动是错误的。该提案显示了一种更好的结构,可以以更通用的方式处理此类问题。

于 2013-03-25T21:49:05.527 回答
1

如果你没有声明一个变量来保存 的返回值TCheck.Create,你就不能访问TCheck.Start(没有TCheck你可以用来访问该Start方法的实例)。

正确的方法是声明一个var Check: TCheck;inside MainT.Execute,然后存储返回的值:

Check := TCheck.Create(Load[i]);  { See note below }
Check.Start;
Inc(tCount);

注意的默认属性TStringListStrings,所以你不需要使用它。您可以Strings像我上面所说的那样直接访问。接下来的两行完全相同(但显然其中一行更短且更易于键入):

Load.Strings[i];
Load[i];

如果您不想保留对 的引用TCheck,只需将您的代码更改为一个with块(包括begin..end, 并且块中不包含其他代码(这是我推荐使用的唯一方法witha ):

with TCheck.Create(Load[i]) do
begin
  Start;
  Inc(tCount);
end;

话虽如此,有更好的方法可以做到这一点,而不是创建/销毁各种线程。正如其他人所说,您可以拥有一个包含 10 个线程的列表并将其工作排队,以便每个线程处理一个项目Load,然后在完成后返回以获取另一个要处理的项目,并重复直到列表完成。很难确切地说你将如何做到这一点,因为这取决于你的 Delphi 版本。(有一些库可以为您完成大部分工作,例如OMNIThreadLibrary,但它不适用于某些旧版本的 Delphi。Delphi 的最新版本还支持TQueueTObjectQueue以及其他一些可能非常有用的类型和功能。

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

于 2013-03-25T19:04:56.857 回答