最好使用TThread的“同步”或者在主线程和子线程之间使用IPC消息? [英] Is it better to use TThread's "Synchronize" or use Window Messages for IPC between main and child thread?

查看:140
本文介绍了最好使用TThread的“同步”或者在主线程和子线程之间使用IPC消息?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个非常简单的多线程VCL gui应用程序用Delphi 2007编写。我在多个子线程(最多16个并发)中进行一些处理,需要更新主窗体上的网格控件(简单地将字符串发布到格)。我的初始设计涉及到调用 TThread的同步来更新当前正在运行的线程中的网格控制表单。但是,我明白,调用Synchronize基本上执行,就好像它是调用时的主线程一样。最多16个线程一次运行(并且大多数子线程的处理从< 1秒到〜10秒)将窗口消息更好的设计?



我已经得到它的工作,在这一点,子线程发布一个Windows消息(由几个字符串的记录组成),主线程有一个监听器,只是在接收到消息时更新网格。



在这种情况下,对IPC的最佳方法有什么意见?窗口消息或同步?



如果我使用窗口消息,建议在TCriticalSection(进入和离开)块中将代码发布到网格中?或者我不需要担心线程安全性,因为我在主线程中写入网格(尽管在窗口消息处理程序的函数内)?

解决方案

编辑:



看起来很多实现细节自Delphi 4和5(Delphi版本我仍然在使用我的大部分工作),而Allen Bauer已经评论过以下内容:


自从D6以来,TThread不再次使用SendMessage。它使用线程安全的工作队列,其中放置用于主线程的工作。一条消息被发布到主线程以指示工作可用并且后台线程阻塞事件。当主消息循环即将空闲时,它调用CheckSynchronize来查看是否有任何工作正在等待。如果是这样,它处理它。工作项完成后,设置后台线程被阻止的事件表示完成。在D2006的时间段中引入了TThread.Queue方法,该方法不会被阻止。


感谢您的更正。所以用原来的答案详细介绍一些盐。



但这并没有真正影响到核心点。我仍然认为, Synchronize()的整个想法是非常有缺陷的,当一个人尝试保留现代机器的几个核心的时候,这是很明显的。不要同步您的线程,让他们工作,直到它们完成。尽量减少它们之间的所有依赖关系。特别是当更新GUI时,绝对不要等待这个完成。是否 Synchronize()使用 SendMessage() PostMessage(),所得到的路障是一样的。






c $ c> Synchronize()在内部使用 SendMessage()。所以这更是一个问题,你想用什么武器来拍摄自己的脚。



Synchronize() TThread 以来,我们一直在与我们在一起,这真是一个耻辱,因为它是VCL中更大的设计错误之一。



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



那么它有什么问题(以及直接使用 SendMessage()的错误呢?几个东西:




  • 强制任何线程在另一个线程的上下文中执行代码强制两个线程上下文切换,这不必要地刻录CPU周期。 / li>
  • 当VCL线程处理消息以调用synchronized方法时,它不能处理任何其他消息。

  • 当多个线程使用此方法他们将所有阻止并等待 Synchronize() SendMessage()返回。这造成了巨大的瓶颈。

  • 有一个等待发生的僵局。如果线程在持有同步对象时调用 Synchronize() SendMessage(),并且处理消息需要获取应用程序将锁定的同一个同步对象。

  • 同样可以说等待线程句柄的API调用 - 使用 WaitForSingleObject() WaitForMultipleObjects()没有一些方法来处理消息将导致死锁,如果线程需要这些方式与其他线程同步 / li>


那么使用什么呢?几个选项,我会描述一些:




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


  • 创建线程安全数据结构,将数据从工作线程放入它们,并从主线程中消耗它们。仅使用 PostMessage()来提醒VCL线程新数据已到达要处理,但不要每次发布消息。如果您有连续的数据流,您甚至可以对数据进行VCL线程轮询(可能通过使用计时器),但这只是一个穷人的版本。


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




编辑:



重新阅读时,我意识到您不打算使用 SendMessage() PostMessage() - 好的,上面的一些不适用,但我会把它留在原地。但是,您想要解决的问题还有一些要点:


一次运行多达16个线程(大部分小时线程的处理时间为< 1秒〜〜10秒)Window消息是否更好?


从每个线程每秒一次甚至更长的时间,那么设计是好的。您不应该做的是每秒线程发布数百条或更多条消息,因为Windows消息队列具有有限的长度,并且自定义消息不应干扰正常的消息处理太多(您的程序将开始显示无响应)。


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


窗口消息不能包含记录。它带有两个参数,其中一个类型为 WPARAM ,另一个为 LPARAM 。您只能将这样的记录的指针投射到这些类型之一,因此记录的生命周期需要以某种方式进行管理。如果你动态分配它,你也需要释放它,这很容易出错。如果您将指针传递到堆栈上的记录或对象字段,则需要确保在处理消息时仍然有效,这比发送的消息更为困难。


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


没有必要这样做,因为 PostMessage()调用将立即返回,因此此时不需要同步 。你肯定需要担心线程安全,不幸的是,你不能在 中知道。您必须确保使用同步对象,通过永久使用锁定数据进行访问来确保对数据的访问是线程安全的。没有真正的方法来实现记录,数据可以直接访问。


I have a rather simple multi-threaded VCL gui application written with Delphi 2007. I do some processing in multiple child threads (up to 16 concurrent) that need to update a grid control on my main form (simply posting strings to a grid). None of the child threads ever talk to each-other.

My initial design involved calling TThread's "Synchronize" to update the grid control form within the currently running thread. However, I understand that calling Synchronize essentially executes as if it is the main thread when called. With up to 16 threads running at once (and most of the child thread's processing takes from < 1 second to ~10 seconds) would Window Messages be a better design?

I've gotten it working at this point where the child thread posts a windows message (consisting of a record of several strings) and the main thread has a listener and simply updates the grid when a message is received.

Any opinions on the best method for IPC in this situation? Window messages or 'Synchronize'?

If I use window messages, do you suggest wrapping the code where I post to the grid in a TCriticalSection (enter and leave) block? Or will I not need to worry about thread safety since I'm writing to the grid in the main thread (although within the window message handler's function)?

解决方案

Edit:

It looks like many of the implementation details have changed since Delphi 4 and 5 (the Delphi versions I'm still using for most of my work), and Allen Bauer has commented the following:

Ever since D6, TThread doesn't use SendMessage anymore. It uses a thread-safe work queue where the "work" intended for the main thread is placed. A message is posted to the main thread to indicate that work is available and the background thread blocks on an event. When the main message loop is about to go idle, it calls "CheckSynchronize" to see if any work is waiting. If so, it processes it. Once a work item is completed, the event on which the background thread is blocked is set to indicate completion. Introduced in D2006 timeframe, TThread.Queue method was added that doesn't block.

Thanks for the correction. So take the details in the original answer with a grain of salt.

But this doesn't really affect the core points. I still maintain that the whole idea of Synchronize() is fatally flawed, and this will be obvious the moment one tries to keep several cores of a modern machine occupied. Don't "synchronize" your threads, let them work until they are finished. Try to minimize all dependencies between them. Especially when updating the GUI there is absolutely no reason to wait for this to complete. Whether Synchronize() uses SendMessage() or PostMessage(), the resulting road block is the same.


What you present here is not an alternative at all, as Synchronize() uses SendMessage() internally. So it's more of a question which weapon you want to use to shoot yourself in the foot with.

Synchronize() has been with us since the introduction of TThread in the Delphi 2 VCL, which is a shame really as it is one of the bigger design misfeatures in the VCL.

How does it work? It uses a SendMessage() call to a window that was created in the main thread, and sets the message parameters to pass the address of a parameterless object method to be called. Since Windows messages will be processed only in the thread that created the destination window and runs its message loop this will suspend the thread, handle the message in the context of the main VCL thread, call the method, and resume the thread only after the method has finished executing.

So what's wrong with it (and what's similarly wrong with using SendMessage() directly)? Several things:

  • Forcing any thread to execute code in the context of another thread forces two thread context switches, which needlessly burns CPU cycles.
  • While the VCL thread processes the message to call the synchronized method it can't process any other message.
  • When more than one thread uses this method they will all block and wait for Synchronize() or SendMessage() to return. This creates a giant bottleneck.
  • There is a deadlock waiting to happen. If the thread calls Synchronize() or SendMessage() while holding a synchronization object, and the VCL thread while processing the message needs to acquire the same synchronization object the application will lock up.
  • The same can be said of the API calls waiting for the thread handle - using WaitForSingleObject() or WaitForMultipleObjects() without some means to process messages will cause a deadlock if the thread needs these ways to "synchronize" with the other thread.

So what to use instead? Several options, I'll describe some:

  • Use PostMessage() instead of SendMessage() (or PostThreadMessage() if the two threads are both not the VCL thread). It is important though to not use any data in the message parameters that will be no longer valid when the message arrives, as the sending and receiving thread are not synchronized at all, so some other means have to be used to make sure that any string, object reference or chunk of memory are still valid when the message is processed, even though the sending thread may not even exist any more.

  • Create thread-safe data structures, put data to them from your worker threads, and consume them from the main thread. Use PostMessage() only to alert the VCL thread that new data has arrived to be processed, but don't post messages each time. If you have a continuous stream of data you could even have the VCL thread poll for data (maybe by using a timer), but this is a poor man's version only.

  • Don't use the low level tools at all, any more. If you are at least on Delphi 2007, download the OmniThreadLibrary and start to think in terms of tasks, not threads. This library has a lot of facilities for data exchange between threads and synchronization. It also has a thread pool implementation, which is a good thing - how much threads you should use does not only depend on the application but also on the hardware it's running on, so many decisions can be made at runtime only. OTL will allow you to run tasks on a thread pool thread, so the system can tune the number of concurrent threads at runtime.

Edit:

On re-reading I realize that you don't intend to use SendMessage() but PostMessage() - well, some of the above doesn't apply then, but I will leave it in place. However, there are some more points in your question I want to address:

With up to 16 threads running at once (and most of the child thread's processing takes from < 1 second to ~10 seconds) would Window Messages be a better design?

If you post a message from each thread once every second or even longer period, then the design is fine. What you should not do is post hundreds or more messages per thread per second, because the Windows message queue has a finite length and custom messages should not interfere with normal message processing too much (your program would start to appear unresponsive).

where the child thread posts a windows message (consisting of a record of several strings)

A window message can not contain a record. It carries two parameters, one of type WPARAM, the other of type LPARAM. You can only cast a pointer to such a record to one of these types, so the lifetime of the record needs to be managed somehow. If you dynamically allocate it you need to free it too, which is prone to errors. If you pass a pointer to a record on the stack or to a object field you need to make sure it is still valid when the message is processed, which is more difficult for posted messages than for sent messages.

do you suggest wrapping the code where I post to the grid in a TCriticalSection (enter and leave) block? Or will I not need to worry about thread safety since I'm writing to the grid in the main thread (although within the window message handler's function)?

There's no need to do this, as the PostMessage() call will return immediately, so no synchronization is necessary at this point. You will definitely need to worry about thread safety, unfortunately you can't know when. You have to make sure that access to data is thread-safe, by always locking the data for access, using synchronization objects. There isn't really a way to achieve that for records, the data can always be accessed directly.

这篇关于最好使用TThread的“同步”或者在主线程和子线程之间使用IPC消息?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆