【问题标题】:Synchronizing/sending data between threads在线程之间同步/发送数据
【发布时间】:2011-08-01 19:32:01
【问题描述】:

该应用程序是用 Delphi XE 编写的。

我有两个类,一个 TBoss 和 TWorker,它们都是基于 TThread 的。 TBoss是单实例线程,启动后会创建大约20个TWorker线程。

当老板创建一个 TWorker 实例时,它会为其分配一个调用同步的方法,当 Worker 完成它正在做的事情时,它会调用此方法,该方法允许 Boss 访问 Worker 上的记录。

但是我觉得这是个问题,调用 synchronize 似乎会锁定整个应用程序 - 阻塞主 (ui) 线程。真的应该只是将那个工人同步到老板线程....

以前我使用消息/打包记录在运行良好的线程之间发送内容。但是这样做会更干净,更好......只是非常阻塞。

有没有办法在worker中调用Syncronize只等待Boss线程?

我的代码:

    type 
      TWorker = class(TThread) 
      private 
        fResult : TResultRecord;
        procedure SetOnSendResult(const Value: TNotifyEvent);
        ....
        ....
      public
        property OnSendResult: TNotifyEvent write SetOnSendResult; 
        property Result : TResultRecord read fResult;
        ....
     end;

    ...
    ...
    procedure TWorker.SendBossResults; 
    begin 
      if (Terminated = False) then 
      begin 
        Synchronize(SendResult); 
      end; 
    end; 

    procedure TWorker.SendResult; 
    begin 
      if (Terminated = false) and Assigned(FOnSendResult) then 
      begin 
        FOnSendResult(Self); 
      end; 
    end;

然后在我的 Boss 线程中我会做这样的事情

    var 
      Worker  : TWorker; 
    begin 
      Worker              := TWorker.Create; 
      Worker.OnTerminate  := OnWorkerThreadTerminate; 
      Worker.OnSendResult := ProcessWorkerResults;

所以我的老板有一个名为 ProcessWorkerResults 的方法——这就是在 Synchronize(SendResult); 上运行的方法。工人。

    procedure TBoss.ProcessWorkerResults(Sender: TObject); 
    begin 
      if terminated = false then 
      begin 
        If TWorker(Sender).Result.HasRecord then
        begin
          fResults.Add(TWorker(Sender).Result.Items);
        end;
      end; 
    end;

【问题讨论】:

  • 它是哪个 Delphi 版本(自 2009 年以来有一些新选项)?
  • 我认为使用消息在线程之间传递数据,它不需要“应用程序”同步机制(它在线程“框架”中),比在共享数据周围使用同步原语要优雅得多.

标签: multithreading delphi synchronization delphi-xe


【解决方案1】:

Synchronize 专门设计用于在 ma​​in 线程中执行代码;这就是为什么它似乎把所有东西都锁起来了。

您可以使用多种方式从工作线程与老板线程进行通信:

  • 为每个工作线程添加回调, 并从老板线程分配它 当它被创建时。它可以传回去 任何作为参数,以及 线程 ID 或其他标识符。

  • 从工作线程发布消息 到老板线程使用 PostThreadMessage。这 这里的缺点是老板 线程必须有一个窗口句柄 (见Classes.AllocateHWnd Delphi 帮助和 David Heffernan 在下面的评论)。

  • 使用质量好的第三方 线程库。看 OmniThreadLibrary - 免费, 操作系统,而且写得非常好。

我的选择是第三个。 Primoz 为您完成了所有的辛苦工作。 :)

在您发表评论后,这里有一些与我的第一个建议类似的内容。请注意,这是未经测试的,因为对于我现在所拥有的时间来说,为 TBoss 和 TWorker 线程 + 一个测试应用程序编写代码有点长......这应该足以给你要点,我希望。

type 
  TWorker = class(TThread) 
  private 
    fResult : TResultRecord;
    fListIndex: Integer;
    procedure SetOnSendResult(const Value: TNotifyEvent);
    ....
    ....
  public
    property OnSendResult: TNotifyEvent write SetOnSendResult; 
    property Result : TResultRecord read fResult;
    property ListIndex: Integer read FListIndex write FListIndex;
    ....
  end;

type 
  TBoss=class(TThread)
  private
    FWorkerList: TThreadList; // Create in TBoss.Create, free in TBoss.Free
    ...
  end;

procedure TWorker.SendBossResults; 
begin 
  if not Terminated then
    SendResult; 
end;

procedure TBoss.ProcessWorkerResults(Sender: TObject); 
var
  i: Integer;
begin 
  if not terminated then 
  begin 
    If TWorker(Sender).Result.HasRecord then
    begin
      FWorkerList.LockList;
      try
        i := TWorker(Sender).ListIndex;
        // Update the appropriate record in the WorkerList
        TResultRecord(FWorkerList[i]).Whatever...
      finally
        FWorkerList.UnlockList;
      end;
    end;
  end; 
end;

【讨论】:

  • 注意非线程安全的 Classes.AllocateHWnd。您需要对 AllocateHWnd 和 DeallocateHWnd 的调用进行序列化。我不知道为什么 VCL 不这样做,在我的代码中我挂钩这些例程以实现线程安全。
  • @David:我知道。 :( 我应该特别提到它作为另一个缺点。感谢您的更正。
  • 您好,是的,这似乎是导致我的问题的原因。但是,您的第一个解决方案是“为每个工作线程添加回调”,这基本上是我所做的,那么它有什么不同呢?
  • @Wizzard:是的。您唯一需要担心的是 TBoss 同时从不同线程获得多个调用——这就是迁移到 TThreadList 的原因。您还可以使用其他方法,例如 TCriticalSection。有关其他可能性,请参阅 Delphi 的 SyncObjs 单元。
  • 请更正您的答案 - 线程确实不需要需要窗口句柄才能接收 Windows 消息。您链接到的 API 文档也说了这么多,消息被发布到由 id 标识的线程,而不是窗口句柄,并且线程需要一个消息队列并且必须处理排队的消息,但就是这样。
【解决方案2】:

您可以使用线程安全队列。在 DelphiXE 中有 TThreadedQueue。如果您没有 DXE,请尝试 OmniThreadLibray - 这个库非常适合所有线程问题。

【讨论】:

  • TThreadedQueue 在有许多消费者或许多生产者的多线程环境中存在问题。改用更轻量级的队列。见link
  • 我不确定这如何回答“有没有办法在工作人员中调用 Syncronize 以仅等待 Boss 线程?” - 你能解释一下吗?
【解决方案3】:

正如我在 Delphi 2009 及更高版本中提到的新选项,以下是我博客中基于新对象锁的线程间生产者/消费者通信示例的链接:

Thread Synchronization with Guarded Blocks in Delphi

note regarding the deprecated methodsTThread.Suspend 和 TThread.Resume,Delphi 的 Embarcadero DocWiki 建议“线程 同步技术应该是 基于 SyncObjs.TEvent 和 SyncObjs.TMutex.“但是, 另一个同步类 自 Delphi 2009 起可用:TMonitor。 它使用对象锁 在这个版本中引入...

【讨论】:

  • 为什么 TMutex 比临界区更受欢迎?我觉得很奇怪。
【解决方案4】:

TWorker 类的public 属性必须具有getset 方法,因此您可以使用Tcriticalsection 来给出属性的值。否则,您将遇到线程安全问题。您的示例看起来不错,但在现实世界中,数千个线程访问相同的值会导致读取错误。使用关键部分..您不必使用任何同步。这样您就可以避免进入窗口的消息队列并提高性能。此外,如果您在 Windows 服务应用程序中使用此代码(不允许使用 Windows 消息),则此示例将不起作用。除非可以访问 Windows 消息队列,否则同步方法不起作用。

【讨论】:

  • Windows 服务可以有 Windows 消息。所有 Windows 应用的核心都包含一个消息泵
【解决方案5】:

已解决!!(来自问题的答案)
针对这个问题进行了两次修复。
先去掉 TWorker SendBossResult 方法中的同步调用。

第二次向 TBoss 类添加一个 fProcessWorkerResult CritalSection。在 TBoss 的创建/销毁中创建和释放它。在 ProcessWorkerResults 方法中调用 fProcessWorkerResult.Enter 和 fProcessWorkerResult.leave 围绕需要安全的代码,以防止多个工作人员结果流入。

以上是 Kens 代码和后续评论后的结论。非常感谢好心的先生,向您致敬!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-14
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 2011-05-05
    • 1970-01-01
    • 2014-11-02
    • 2013-08-10
    相关资源
    最近更新 更多