Mic*_*ick 21 delphi multithreading ipc
我有一个用Delphi 2007编写的相当简单的多线程VCL gui应用程序.我在多个子线程(最多16个并发)中进行一些处理,需要更新主窗体上的网格控件(只需将字符串发布到网格中).没有任何子线程彼此交谈.
我的初始设计涉及调用TThread的"Synchronize"来更新当前运行的线程中的网格控件表单.但是,据我所知,调用Synchronize实际上就好像它是调用时的主线程一样.一次运行多达16个线程(并且大多数子线程的处理时间从<1秒到10秒),Window Messages是一个更好的设计吗?
我已经让它工作在这一点,子线程发布一个Windows消息(由几个字符串的记录组成),主线程有一个监听器,只是在收到消息时更新网格.
在这种情况下对IPC的最佳方法有何看法?窗口消息或'同步'?
如果我使用窗口消息,您是否建议将代码包装在我在TCriticalSection(进入和离开)块中发布到网格的位置?或者我不需要担心线程安全,因为我在主线程中写入网格(虽然在窗口消息处理程序的函数内)?
mgh*_*hie 36
编辑:
看起来自Delphi 4和5(Delphi版本我仍在使用的大部分工作中)后,许多实现细节都发生了变化,而Allen Bauer评论了以下内容:
从D6开始,TThread不再使用SendMessage了.它使用线程安全的工作队列,其中放置了用于主线程的"工作".将消息发布到主线程以指示工作可用且后台线程阻止事件.当主消息循环即将空闲时,它会调用"CheckSynchronize"以查看是否有任何工作正在等待.如果是这样,它会处理它.完成工作项后,将阻止后台线程的事件指示完成.在D2006时间帧中引入,添加了不阻止的TThread.Queue方法.
谢谢你的纠正.因此,请在原始答案中详细说明.
但这并不会真正影响核心要点.我仍然认为整个想法都Synchronize()存在致命的缺陷,而且当一个人试图保持现代机器的几个核心被占用时,这将是显而易见的.不要"同步"你的线程,让它们工作直到它们完成.尽量减少它们之间的所有依赖关系.特别是在更新GUI时,绝对没有理由等待它完成.无论是Synchronize()使用SendMessage()还是PostMessage(),产生的路障都是一样的.
你在这里所介绍的不是在所有的替代方案,如Synchronize()采用SendMessage()内部.所以这更像是一个问题,你想用什么武器射击自己的脚.
Synchronize()自从TThreadDelphi 2 VCL 推出以来,我们就一直与我们在一起,这真是一个耻辱,因为它是VCL中更大的设计错误之一.
它是如何工作的?它使用SendMessage()对主线程中创建的窗口的调用,并设置消息参数以传递要调用的无参数对象方法的地址.由于Windows消息将仅在创建目标窗口的线程中处理并运行其消息循环,因此将挂起线程,在主VCL线程的上下文中处理消息,调用方法,并仅在方法之后恢复线程已完成执行.
那么它有什么问题(以及SendMessage()直接使用的同样错误)?几件事:
Synchronize()或SendMessage()返回.这造成了巨大的瓶颈.Synchronize()或SendMessage()持有同步对象,并且处理消息时VCL线程需要获取相同的同步对象,则应用程序将锁定.WaitForSingleObject()或WaitForMultipleObjects()不使用某种方法处理消息将导致死锁.那么使用什么呢?几个选项,我将描述一些:
使用PostMessage()而不是SendMessage()(或者PostThreadMessage()如果两个线程都不是VCL线程).重要的是,不要在消息参数中使用任何数据,这些数据在消息到达时将不再有效,因为发送和接收线程根本不同步,因此必须使用其他一些方法来确保任何字符串处理消息时,对象引用或内存块仍然有效,即使发送线程可能不再存在.
创建线程安全的数据结构,从工作线程将数据放入它们,并从主线程中使用它们.使用PostMessage()仅提醒VCL线程新的数据已经到达要处理,但每次不发帖.如果您有连续的数据流,您甚至可以对数据进行VCL线程轮询(可能使用计时器),但这只是一个穷人的版本.
根本不要使用低级工具.如果您至少在Delphi 2007上,请下载OmniThreadLibrary并开始考虑任务,而不是线程.该库具有许多用于线程和同步之间的数据交换的工具.它还有一个线程池实现,这是一件好事 - 你应该使用多少线程不仅取决于应用程序,还取决于它运行的硬件,因此很多决定只能在运行时进行.OTL将允许您在线程池线程上运行任务,因此系统可以在运行时调整并发线程的数量.
编辑:
在重新阅读时,我意识到你不打算使用SendMessage()但是PostMessage()- 好吧,上面的一些不适用,但我会把它留在原地.但是,我想提出的问题还有一些要点:
一次运行多达16个线程(并且大多数子线程的处理时间从<1秒到10秒),Window Messages是一个更好的设计吗?
如果您每隔一秒甚至更长时间从每个帖子发布一条消息,那么设计就可以了.你不应该做的是每秒每个线程发布数百或更多的消息,因为Windows消息队列的长度有限,自定义消息不应该过多干扰正常的消息处理(你的程序会开始显示无响应).
子线程发布一条Windows消息(由几个字符串的记录组成)
窗口消息不能包含记录.它带有两个参数,一个是类型WPARAM,另一个是类型LPARAM.您只能将指向此类记录的指针强制转换为其中一种类型,因此需要以某种方式管理记录的生命周期.如果你动态分配它,你也需要释放它,这很容易出错.如果将指针传递给堆栈上的记录或对象字段,则需要确保在处理消息时它仍然有效,这对于发布的消息比发送的消息更难.
你建议在我在TCriticalSection(进入和离开)块中发布到网格的代码吗?或者我不需要担心线程安全,因为我在主线程中写入网格(虽然在窗口消息处理程序的函数内)?
有没有必要做这个,因为PostMessage()调用将立即返回,所以不需要同步在这一点上.你肯定需要担心线程安全,遗憾的是你无法知道什么时候.您必须始终使用同步对象锁定要访问的数据,以确保对数据的访问是线程安全的.实际上没有办法实现记录,数据总是可以直接访问.
顺便说一句,你也可以用TThread.Queue()而不是TThread.Synchronize(). Queue()是异步版本,它不会阻止调用线程:
(Queue自D8起可用).
我更喜欢Synchronize()或者Queue(),因为它更容易理解(对于其他程序员)和更好的OO而不是简单的消息发送(没有控制它或能够调试它!)
虽然我确信有正确的方法和错误的方式.我用两种方法编写代码,而我一直在返回的是SendMessage方法,我不知道为什么.
SendMessage vs Synchronize的使用并没有什么区别.两者的工作基本相同.我认为我继续使用SendMessage的原因是我感觉到更多的控制,但我不知道.
SendMessage例程使调用线程暂停并等待,直到目标窗口完成处理发送的消息.因此,主要应用程序线程在调用期间基本上与调用子线程同步.您不需要在Windows消息处理程序中使用临界区.
从调用线程到主应用程序线程,数据传输基本上是一种方式.您可以在message.result中返回整数类型值,但不指向主线程中的内存对象.
由于这两个线程是"同步"的,因此该点与主应用程序线程当前绑定响应SendMessage,那么您也不需要担心其他线程进入并同时丢弃您的数据.因此,您不必担心使用关键部分或其他类型的线程安全措施.
对于简单的事情,您可以定义单个消息(wm_threadmsg1)并使用wparam和lparam字段来回传输(整数)状态消息.对于更复杂的示例,您可以通过将其传递给lparam并将其转换回longint来传递字符串.A-la longint(pchar(myvar))或使用pwidechar如果您使用D2009或更新版本.
如果您已经使用了Synchronize方法,那么我不会担心重新进行更改.