代码之家  ›  专栏  ›  技术社区  ›  Mick

在主线程和子线程之间使用tthread的“同步”还是使用ipc的窗口消息更好?

  •  21
  • Mick  · 技术社区  · 15 年前

    我有一个用Delphi2007编写的非常简单的多线程VCL GUI应用程序。我在多个子线程(最多16个并发线程)中执行一些处理,这些线程需要更新主窗体上的网格控件(只需将字符串发布到网格)。孩子们的线从来没有互相交谈过。

    我最初的设计是打电话 TThread's "Synchronize" 更新当前运行线程中的网格控件窗体。但是,我理解调用synchronize本质上执行起来就像它是被调用时的主线程一样。一次最多运行16个线程(子线程的大部分处理时间从<1秒到~10秒),窗口消息会是更好的设计吗?

    我已经让它在这个点上工作了,在这个点上,子线程发布一条Windows消息(由多个字符串的记录组成),主线程有一个监听器,并且在收到消息时简单地更新网格。

    在这种情况下,对IPC的最佳方法有何看法?窗口消息还是“同步”?

    如果我使用窗口消息,是否建议将我发布到网格中的代码包装在tcriticalsection(Enter和Leave)块中?或者我不需要担心线程安全,因为我在主线程中写入网格(尽管在窗口消息处理程序的函数中)?

    3 回复  |  直到 8 年前
        1
  •  36
  •   R. Hoek    8 年前

    编辑:

    看起来自Delphi4和Delphi5(我大部分工作仍在使用Delphi4和Delphi5版本)以来,许多实现细节都发生了变化,AllenBauer评论了以下内容:

    从d6开始,tthread不再使用sendmessage。它使用一个线程安全工作队列,其中放置了用于主线程的“工作”。一条消息被发送到主线程,以指示工作可用,并且后台线程在事件上阻塞。当主消息循环即将空闲时,它调用“checksynchronize”来查看是否有工作在等待。如果是这样,它会处理它。工作项完成后,将阻止后台线程的事件设置为指示完成。在d2006时间框架中引入了不阻塞的tthread.queue方法。

    谢谢你的纠正。所以,用一粒盐把原始答案中的细节记下来。

    但这并不真正影响核心点。我仍然认为 Synchronize() 是致命的缺陷,这将是显而易见的时刻,一个人试图保持几个核心的现代机器占用。不要“同步”线程,让它们工作到完成为止。尽量减少它们之间的依赖关系。尤其是在更新GUI时 等待此操作完成的原因。是否 同步() 使用 SendMessage() PostMessage() ,产生的路障是相同的。


    你在这里所呈现的完全不是另一种选择,因为 同步() 使用 sEnMeDebug() 内部的。所以这更像是一个问题,你想用哪种武器向自己的脚开枪。

    同步() 自从 TThread 在Delphi2VCL中,这真是一个遗憾,因为它是VCL中最大的设计缺陷之一。

    它是如何工作的?它使用了 发送消息() 调用在主线程中创建的窗口,并设置消息参数以传递要调用的无参数对象方法的地址。由于Windows消息将只在创建目标窗口并运行其消息循环的线程中处理,因此这将挂起线程,在主VCL线程的上下文中处理消息,调用该方法,并仅在该方法完成执行后恢复线程。

    所以,它有什么问题(以及使用它同样有什么问题) sEnMeDebug() 直接)?几件事:

    • 强制任何线程在另一个线程的上下文中执行代码会强制执行两个线程上下文切换,这将不必要地消耗CPU周期。
    • VCL线程处理消息以调用synchronized方法时,它无法处理任何其他消息。
    • 当多个线程使用此方法时,它们将 全部的 阻止并等待 同步() sEnMeDebug() 返回。这就造成了一个巨大的瓶颈。
    • 有一个死锁等待着发生。如果线程调用 同步() sEnMeDebug() 当持有同步对象时,VCL线程在处理消息时需要获取相同的同步对象,应用程序将被锁定。
    • 对于等待线程句柄的API调用也可以这样说-使用 WaitForSingleObject() WaitForMultipleObjects() 如果线程需要这些方法来与另一个线程“同步”,那么如果没有一些方法来处理消息,将导致死锁。

    那么用什么来代替呢?我将介绍几个选项:

    • 使用 邮递消息() 而不是 sEnMeDebug() (或) PostThreadMessage() 如果两个线程都不是VCL线程)。但重要的是,不要在消息到达时不再有效的消息参数中使用任何数据,因为发送和接收线程根本不同步,因此必须使用其他方法确保在处理消息时任何字符串、对象引用或内存块仍然有效,即使发送线程甚至可能不再存在。

    • 创建线程安全的数据结构,从工作线程将数据放到它们上面,然后从主线程使用它们。使用 邮递消息() 只提醒VCL线程新数据已到达要处理的位置,但不要每次都发布消息。如果有连续的数据流,甚至可以对VCL线程进行数据轮询(可能使用计时器),但这只是一个穷人版本。

    • 不要再使用低级工具了。如果您至少在Delphi2007上,请下载 OmniThreadLibrary 开始从任务而不是线程的角度思考。这个库有很多工具可以在线程和同步之间进行数据交换。它还有一个线程池实现,这是一件好事——您应该使用多少线程不仅取决于应用程序,还取决于它运行的硬件,因此许多决定只能在运行时作出。OTL将允许您在线程池线程上运行任务,因此系统可以在运行时调整并发线程的数量。

    编辑:

    重新阅读时,我意识到你不打算使用 sEnMeDebug() 但是 邮递消息() -好吧,那么上面的一些就不适用了,但是我会把它放在适当的地方。不过,我还想谈谈你的问题:

    一次最多运行16个线程(子线程的大部分处理时间从<1秒到~10秒),窗口消息会是更好的设计吗?

    如果您每隔一秒或更长的时间发布来自每个线程的消息,那么设计就可以了。您不应该做的是每秒每线程发送数百条或更多的消息,因为Windows消息队列的长度有限,自定义消息不应太多地干扰正常的消息处理(您的程序将开始看起来没有响应)。

    其中子线程发布Windows消息(由多个字符串的记录组成)

    窗口消息不能包含记录。它有两个参数,一个是类型 WPARAM ,另一种类型 LPARAM . 您只能将指向此类记录的指针强制转换为这些类型之一,因此需要以某种方式管理记录的生存期。如果您动态地分配它,您也需要释放它,这很容易出错。如果将指针传递到堆栈上的记录或对象字段,则需要确保在处理消息时它仍然有效,这对于已发布的消息比发送的消息更为困难。

    您是否建议将我发布到网格的代码包装在tcriticalsection(enter和leave)块中?或者我不需要担心线程安全,因为我在主线程中写入网格(尽管在窗口消息处理程序的函数中)?

    没有必要这样做,因为 后消息() 调用将立即返回,因此无需同步 在这一点上 . 你肯定需要担心线程安全,不幸的是你不知道 什么时候 . 您必须确保对数据的访问是线程安全的,通过 总是 使用同步对象为访问锁定数据。对于记录来说,没有真正的方法可以实现这一点,数据总是可以直接访问的。

        2
  •  9
  •   Remy Lebeau    8 年前

    顺便说一句,你也可以用 TThread.Queue() 而不是 TThread.Synchronize() . Queue() 是异步版本,它不会阻塞调用线程:

    ( Queue 自D8起提供)。

    我更喜欢 Synchronize() 队列() ,因为它(对于其他程序员)比纯消息发送(没有控制权或能够调试它)更容易理解,也更好地实现OO。

        3
  •  5
  •   Vivian Mills    15 年前

    虽然我确信有正确的方法和错误的方法。我已经用这两种方法编写了代码,我一直返回的方法是sendmessage方法,我不知道为什么。

    sendmessage与synchronize的使用实际上没有任何区别。两者的工作原理基本相同。我认为我一直使用sendmessage的原因是我感觉到了更多的控制,但我不知道。

    sendmessage例程使调用线程暂停并等待,直到目标窗口完成对发送的消息的处理。因此,在调用期间,主应用程序线程基本上与调用子线程同步。您不需要使用Windows消息处理程序中的关键部分。

    从调用线程到主应用程序线程,数据传输本质上是一种方式。您可以在message.result中返回一个整型值,但不能指向主线程中的内存对象。

    因为这两个线程是“同步”的,所以现在主应用程序线程被绑定在对sendmessage的响应上,所以您也不需要担心其他线程同时进入和破坏您的数据。所以不需要担心使用关键部分或其他类型的线程安全措施。

    对于简单的事情,您可以定义单个消息(wm_threadmsg1),并使用wparam和lparam字段来回传输(integer)状态消息。对于更复杂的示例,您可以通过lparam传递字符串并将其类型化为longnt来传递字符串。a-la longint(pchar(myvar))或使用pwidechar(如果您使用的是d2009或更新版本)。

    如果您已经让它与同步方法一起工作了,那么我就不必担心修改它来进行更改。

    推荐文章