【问题标题】:Backgroundworking Thread Delphi 2010后台工作线程 Delphi 2010
【发布时间】:2014-01-18 18:31:10
【问题描述】:

谁能告诉我如何在 Delphi 2010 中创建一个线程,该线程将“待机”以供计算数据(在其计算任务后不终止)?

我创建了一个程序,它通过 Indy UDPServer 从外部源获取数据。 IdUDPServer1UDPRead 事件收集数据并调用不同的线程(取决于数据的类型),但是在调试程序时,我看到线程在计算后被终止,然后再次被创建(线程的创建需要一些时间)。 只要传入数据的频率大于 CPU(或线程)可以处理的频率(在线程完成计算之前有数据到来),我是否可以创建同一线程的 fork 线程。


这是我正在尝试编写的代码:

procedure TForm1.IdUDPServer1UDPRead(AThread: TIdUDPListenerThread;
    AData: TBytes;
    ABinding:TIdSocketHandle);                                             
begin   
  form1.panel2.color:=clLime;    
  ParseDelimited(IdUDPServer1.ReceiveString,'&');    
  if (Parsedelimited=1) and (Jvthread1.Terminated=true) then
    Jvthread1.Execute(self)    
  else if (Parsedelimited=2) and (Jvthread2.Terminated=true) then
    Jvthread2.Execute(self);

  Application.ProcessMessages;
  // i know this command is not very good but by removing this line the
  //gui is responding //after 2 or 3 sec
end;

问题是 JVThread1 或 JVThread2 进行计算所需的时间大于传入的数据,我认为这个问题是由一次又一次创建线程所需的时间引起的(也许这是错误的推测)。尽管如此,通过将传入的 UDP 数据缓冲到 Indy UDPServer 可以部分解决这个问题,但是当我尝试关闭 UDPServer 时,直到它的缓冲区完全为空之前什么都没有发生,这大约需要 3-4 秒。

【问题讨论】:

  • 欢迎来到 Stack Overflow。虽然您已努力在问题中包含详细信息,但如果您还可以包含代码会很好。以下是编写好问题的指南。 stackoverflow.com/questions/how-to-ask
  • 当我输入我的答案时,我注意到您添加了另一个答案,这确实是一个问题。 (注意如果您的问题有更多信息,请编辑您的问题。如果是不同的问题。请提出一个全新的问题。)至于您的问题:“线程安全”是一个非常具体的问题主题和Sleep 完全不相关。然而Sleep 虽然不是很糟糕,但通常被认为是一种笨拙的解决方法,因为您要停止一个进程一段固定的时间。如果需要,您宁愿能够被触发唤醒。我的答案列出了Sleep 的更好替代方案。
  • @TwentyGotoTen 我个人对此类更高级别的问题没有意见。 OP 不确定有哪些可用选项,需要一般指导。实际计算的代码仅用于确认已经明确的内容:计算完成后方法结束。

标签: multithreading delphi udp delphi-2010 indy


【解决方案1】:

线程的创建需要一些时间

是的。

只要传入数据的频率更大,我可以创建同一个线程的fork线程吗

这没有意义。您的计算机只能同时执行与(CPU 数量)x(一个 CPU 中的内核数量)一样多的线程。所有其他线程将等待它们的时间片。无论您要创建多少线程 - 可以执行的线程很少。并且创建更多线程 - 只会浪费系统资源。

所以我认为正确的方法是不要终止计算数据帧的线程,而是请求下一个作业加载并在没有立即准备好的作业时进入睡眠状态。

总的来说,我建议你也看看管道模型:将数据处理分成几个阶段。

(internet) -> 输入队列 -> (N 个工作线程,一些正在休眠,一些处于活动状态) -> 半处理数据队列 -> (N 个工作线程,一些正在休眠,一些处于活动状态) -> 完全处理数据队列 - >(M个保存结果的线程)

N 受您的 CPU 内核总数的限制,正如我上面所说的。 M 受可用终端存储的限制。通常是单个 HDD 上的单个数据库,因此 M==1,但有时存储可能会分片到不同的服务器或不同的磁盘。

如果您的数据属于不同类型,您可以分叉流,以便某个阶段将结果输出到由不同工作池处理的两个不同队列中,根据数据类型来判断。

诀窍是安排一个易于使用的框架,使线程可以轻松进入睡眠状态,并随着输入数据的传输而自动唤醒。 OmniThread 库可能是一个可能的解决方案,因为您使用的是 Delphi 2010:http://otl.17slon.com/book/doku.php?id=book:highlevel:pipeline


在阅读http://robertocschneiders.wordpress.com/2012/11/22/datasnap-analysis-based-on-speed-stability-tests/ 之后,似乎 Indy 并未针对多连接应用程序进行优化。由于 UDP 是无会话协议,我希望为任何新连接打开一个新会话,并且可能会显示出类似的性能限制。

我建议您尝试在不同平台上设计一个假服务器,并尝试对其进行重载 (DDoS) 以比较不同库允许的最大吞吐量。 Indy 是其中之一,然后是 OverByte ICS 和 Ararat Synapse,还有 Synopse mORMot。如果您以管道模式设计您的程序,那么我认为您将能够轻松地使用任何其他库切换初始 TCP/UDP 输入阶段。但是,也许您可​​以在编写其余代码之前证明您的库最适合您的应用程序目标?但是你的初始阶段是非常简单的 - 只是接收数据并将其放入队列而不分析,让进一步的阶段是调度员查看接收到的数据类型并将其发送到不同的处理队列。

【讨论】:

  • 非常感谢。但由于我是 delphi 的新手,我想问一下:我怎样才能让 Delphi TThread 或 Jedi JVThread 进入睡眠状态并自动唤醒线程?你能给我一个简单的编码例子吗?我在某处(我认为是在 embarcadero 网站)读到,在 Delphi 2010 中休眠线程是不安全的!是真的吗?
  • 我在 OTL wiki 上给了你例子。它有很多控制工作线程的数据队列的例子。我不能说 d2010 是否擅长多线程(d2009 很糟糕),但 OTL 使用自己的管理而不是 tthread,尝试阅读教程可能会有意义。
【解决方案2】:

由于您的问题是可以理解的高层次的,我将提供一个高层次的答案,它应该为您指明正确的方向。如果您有更具体的问题,请先检查是否有人已经问过该问题;如果没有,请随时提出新问题。

无论何时实现一个线程,您都可以完全控制该线程的作用。所以是的,如果您的线程执行方法只是执行计算然后结束,您的线程将终止。如果您想保持线程处于活动状态,只需确保该方法不会结束。

最简单的方法是使用while True do;警告 像这样将你的线程放入一个简单的无限循环实际上是非常糟糕,因为它会引入一些严重的问题。您确实需要一个无限循环来保持线程在多个计算可用时处于活动状态;但是,您需要额外的代码来解决以下问题。

  • 像这样的简单循环只会占用处理器的核心(即使循环什么也不做)。这是对资源的浪费,并且会对其他应用程序产生负面影响。您真正想要的是线程仅在实际有工作要做时才进行处理;否则将被停止/暂停。
    • 这可以通过使用 Windows API 调用(或 Delphi 等效项)来完成,该调用告诉线程停止直到发生其他事情。
    • 阅读以下内容:SleepExWaitForSingleObjectTSimpleEvent
  • 第二个严重的问题是这样的循环不能优雅地结束。通过优雅地结束,我们的意思是execute方法在“清理”之后可以正常退出。否则,如果您在执行某项操作的过程中强制它退出,它可能会分配无法释放的资源、锁或内存。
    • 因此,您要在循环中更改的第一件事是,您应该使用while not Terminated,而不是while True。这样,任何引用您的线程的东西都可以设置一个标志,告诉您的线程它必须在适当的时候退出其循环(从而终止)。
    • 请注意,如果您的线程已暂停(根据前面的问题),您还必须确保设置此标志会唤醒线程以真正检查该标志。因此,您可以添加到之前的阅读列表:WaitForMultipleObjects
  • 您必须处理的最后一个问题是向您的线程获取新数据,以便它可以执行计算。通常当人们创建线程来执行一次性任务时,在创建线程时会传递所需的数据。简单地允许主应用程序更新数据的幼稚方法绝对不好!在计算完成之前,您可能会覆盖输入数据;或者更糟糕的是,在计算过程中覆盖一些输入,从而产生不准确和不可预测的结果。
    • 基本上,您需要维护一个输入数据结构列表。每当您收到新数据时,都会将结构添加到列表中。线程将在处理项目时从列表中删除项目。 (此列表通常是 Queue 或 FIFO 集合。)
    • 每当将一个或多个项目添加到队列时,必须触发唤醒事件,以便线程可以再次开始处理。线程应该在返回睡眠之前处理当前队列中的所有项目。
    • 注意!您的 Queue 实现绝对是线程安全的,这一点是绝对必要的。您不希望同时添加和/或删除会破坏您的内部结构。
    • 建议阅读:Message QueuesPostMessage(向标准 Windows 消息队列发送消息)和Command design pattern

旁注

您似乎担心创建线程的时间开销。这表明您的计算本身实际上非常快。这就引出了一个问题,通过使用单独的线程进行计算,您真的有什么收获吗?

您确实也说过您的输入数据已经来自不同的线程。因此,如果您的计算涉及关联来自多个源线程的数据,那么它确实 有意义。我之所以提到这一点,是因为多线程可以加快程序速度是一种非常普遍的编程误解。如果计算速度很快,那么转移到另一个线程来做同样的工作绝对没有意义。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-05
    • 2011-06-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多