更多线程,更好的性能?

时间:2009-04-21 14:21:24

标签: c++ multithreading winapi message-queue

当我编写消息驱动的应用程序时。就像标准的Windows应用程序一样,它只是广泛地使用消息传递进行内部操作,这对线程化的最佳方法是什么?

我认为,基本上有三种方法(如果您有任何其他设置,请分享):

  1. 让一个线程处理所有消息。
  2. 为单独的消息类型(常规,UI,网络等)提供单独的线程
  3. 拥有多个共享和处理单个邮件队列的线程。
  4. 那么,三者之间是否存在显着的性能差异? 以下是一些一般性的想法: 显然,最后两个选项可以从多个处理器的情况中受益。另外,如果任何线程正在等待外部事件,则其他线程仍然可以处理不相关的消息。但忽略这一点,似乎多线程只增加开销(线程切换,更不用说更复杂的同步情况)。

    另一个问题:您是否建议在标准的Windows消息系统上实现这样的系统,或者实现单独的队列机制,为什么?

10 个答案:

答案 0 :(得分:8)

线程模型的具体选择应该由您尝试解决的问题的性质决定。设计这种应用程序的线程模型不一定有一种“正确”的方法。但是,如果我们采用以下假设:

  1. 消息经常到达
  2. 消息是独立的,不会过分依赖共享资源
  3. 希望尽快回复到达的消息
  4. 您希望应用程序在处理体系结构(即多代码/多CPU系统)之间进行良好扩展
  5. 可扩展性是关键设计要求(例如,更快速率的消息)
  6. 适合线程故障/长操作的弹性
  7. 根据我的经验,最有效的线程架构是使用线程池。所有消息都到达单个队列,多个线程在队列上等待并在消息到达时处理消息。线程池实现可以为您拥有的所有三个线程分发示例建模。

    #1单线程处理所有消息=>只有一个线程的线程池

    每N个消息类型的#2个线程=>具有N个线程的线程池,每个线程都在队列中查找以找到适当的消息类型

    #3所有消息的多个线程=>具有多个线程的线程池

    此设计的好处是,您可以根据处理环境或消息负载比例缩放线程中的线程数。线程数甚至可以在运行时扩展,以适应正在经历的实时消息负载。

    大多数平台都有许多好的线程池库,包括.NET,C ++ / STL,Java等。

    关于你的第二个问题,是否使用标准的Windows消息调度机制。这种机制带来了巨大的开销,实际上只用于通过Windows应用程序的UI循环来传送消息。除非这是您要解决的问题,否则我建议不要将其用作一般的消息调度解决方案。此外,Windows消息携带的数据非常少 - 它不是基于对象的模型。每个Windows消息都有一个代码和一个32位参数。这可能不足以基于干净的消息传递模型。最后,Windows消息队列不是设计用于处理队列饱和,线程饥饿或消息重新排队等情况;这些是在实施体面的消息排队解决方案时经常出现的情况。

答案 1 :(得分:3)

在不知道工作量(即事件随时间的统计分布)的情况下,我们无法确切地告诉您,但总的来说

  • 具有多个服务器的单个队列至少同样快,通常更快,因此1,3优于2。
  • 大多数语言中的多个线程增加了复杂性,因为需要避免争用和多个写入器问题
  • 长时间进程可以阻止处理其他可以更快完成的事情。

所以马背猜测是有一个事件队列,有几个服务器线程从队列中取出事件,可能更快一点。

确保为队列使用线程安全的数据结构。

答案 2 :(得分:3)

一切都取决于。

例如:

  • GUI队列中的事件最好由单个线程完成,因为事件中存在隐含顺序,因此需要按顺序完成。这就是为什么大多数GUI应用程序都有一个线程来处理事件,尽管可能有多个事件来创建它们(并且它不会阻止事件线程创建一个作业并将其处理到一个工作池(见下文))。

  • 套接字上的事件可能是并行完成的(假设是HTTP),因为每个请求都是无状态的,因此可以独立完成(好的,我知道这简化了HTTP)。

  • 工作工作每个工作都是独立的并且排在队列中。这是使用一组工作线程的经典案例。每个线程独立于其他线程执行可能很长的操作。完成后返回队列进行另一项工作。

答案 3 :(得分:1)

一般情况下,不要担心线程的开销。如果你只谈论其中的一小部分,那就不会成为一个问题。竞争条件,死锁和争用是一个更大的问题,如果你不知道我在说什么,你在解决这个问题之前需要做很多的阅读。

我会使用选项3,使用我选择的语言提供的任何抽象。

答案 4 :(得分:1)

请注意,有两个不同的性能目标,您尚未说明您的目标:吞吐量和响应能力。

如果您正在编写GUI应用程序,则UI需要具有响应能力。您不关心每秒可以处理多少次点击,但您确实关心的是在十分之一秒内显示一些响应(理想情况下更少)。这是最好有一个专门用于处理GUI的线程的原因之一(在其他答案中已经提到了其他原因)。 GUI线程基本上需要将Windows消息转换为工作项,并让您的工作队列处理繁重的工作。完成工作后,它会通知GUI线程,然后GUI线程会更新显示以反映任何更改。它可以像绘制窗口一样,但不会渲染要显示的数据。这为应用程序提供了快速的“快照”,这是大多数用户在谈论性能时所需要的。他们不关心是否需要15秒才能做一些艰难的事情,只要他们点击按钮或菜单,就会立即做出反应。

另一个性能特征是吞吐量。这是您可以在特定时间内处理的作业数。通常,这种类型的性能调整仅在服务器类型应用程序或其他重型处理上需要。这可以衡量一小时内可以提供多少个网页,或者渲染DVD需要多长时间。对于这些类型的作业,您希望每个CPU有1个活动线程。少于此,你将浪费空闲的时钟周期。更重要的是,线程将争夺CPU时间并相互绊倒。请查看本文DDJ articles中的第二个图表,了解您正在处理的权衡问题。请注意,由于阻塞和锁定等原因,理想的线程数高于可用CPU的数量。关键是活动线程的数量。

答案 5 :(得分:0)

一个好的开始就是问问自己为什么需要多个线程。

对这个问题经过深思熟虑的回答将引导您对后续问题的最佳答案,“我应该如何在我的应用程序中使用多个线程?”

那一定是随后的问题;不是主要问题。第一个问题必须是原因,而不是如何。

答案 6 :(得分:0)

我认为这取决于每个线程的运行时间。每条消息都需要相同的时间来处理吗?或者某些消息会花费几秒钟。如果我知道消息A需要10秒才能完成,我肯定会使用一个新线程,因为为什么我要为长时间运行的线程保留队列......

我的2美分。

答案 7 :(得分:0)

我认为选项2是最好的。让每个线程执行独立任务将为您提供最佳结果。如果多个线程正在执行某些I / O操作(如磁盘读取,读取公共套接字等),则第3种方法可能会导致更多延迟。

是否使用Windows消息传递框架处理请求取决于每个线程可能具有的工作负载。我认为窗户限制了没有。可以排队的消息最多为10000.对于大多数情况,这应该不是问题。但是如果你要排队很多消息,这可能是需要考虑的事情。

单独的队列可以提供更好的控制,您可以按照自己的方式对其进行重新排序(可能取决于优先级)

答案 8 :(得分:0)

是的,您的选择之间会有性能差异。

(1)介绍了一个用于消息处理的瓶颈 (3)引入了锁争用,因为您需要同步对共享队列的访问。

(2)开始向正确的方向发展......尽管每种消息类型的队列都有点极端。我可能建议从应用程序中的每个模型的队列开始,并在其中添加队列,以便提高性能。

如果你喜欢选项#2,听起来你有兴趣实现SEDA architecture。这将需要一些阅读来了解正在发生的事情,但我认为该架构非常符合您的思路。

BTW,Yield是一个很好的C ++ / Python混合实现。

答案 9 :(得分:0)

我有一个服务于消息队列的线程池,并且可以轻松配置池中的线程数(甚至可以在运行时)。然后用预期的负载测试它。

通过这种方式,您可以看到实际的相关性 - 如果您的初始假设发生变化,您可以轻松改变您的方法。

更复杂的方法是让系统反省自己的性能特征,并调整资源的使用,特别是线程的使用。对于大多数自定义应用程序代码来说可能有些过分,但我确信有些产品可以用来实现这一目标。

关于windows事件的问题 - 我认为这可能是一个特定于应用程序的问题,在一般情况下没有正确或错误的答案。也就是说,我通常会实现自己的队列,因为我可以根据手头任务的具体特点进行定制。有时可能涉及通过Windows消息队列路由事件。