5

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

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

  1. 让一个线程处理所有消息。
  2. 为不同的消息类型(通用、UI、网络等)提供不同的线程
  3. 拥有多个共享和处理单个消息队列的线程。

那么,这三者之间会存在显着的性能差异吗?以下是一些一般性想法: 显然,最后两个选项受益于有多个处理器的情况。另外,如果任何线程正在等待外部事件,其他线程仍然可以处理不相关的消息。但是忽略这一点,似乎多个线程只会增加开销(线程切换,更不用说更复杂的同步情况)。

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

4

10 回答 10

8

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

  1. 消息频繁到达
  2. 消息是独立的,不会过分依赖共享资源
  3. 希望尽快响应到达的消息
  4. 您希望应用程序能够跨处理架构(即多代码/多 CPU 系统)很好地扩展
  5. 可扩展性是关键的设计要求(例如,更多消息以更快的速度)
  6. 对线程故障/长时间操作的弹性是可取的

根据我的经验,最有效的线程架构是使用线程池。所有消息都到达一个队列,多个线程在队列上等待并在消息到达时处理它们。线程池实现可以对您拥有的所有三个线程分布示例进行建模。

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

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

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

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

大多数平台都有很多不错的线程池库可用,包括 .NET、C++/STL、Java 等。

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

于 2009-04-21T15:04:31.640 回答
3

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

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

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

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

于 2009-04-21T14:31:09.710 回答
3

这一切都取决于。

例如:

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

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

  • 工作作业是每个作业都是独立的并放在队列中。这是使用一组工作线程的经典案例。每个线程独立于其他线程执行潜在的长时间操作。完成后返回队列等待另一项工作。

于 2009-04-21T14:57:31.707 回答
1

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

如果您正在编写 GUI 应用程序,则 UI 需要具有响应性。您并不关心每秒可以处理多少次点击,但您确实关心在 10 秒左右(理想情况下更少)内显示一些响应。这是最好有一个专门用于处理 GUI 的单个线程的原因之一(其他原因已在其他答案中提到)。GUI 线程需要基本上将 Windows 消息转换为工作项,并让您的工作队列处理繁重的工作。一旦工作人员完成,它会通知 GUI 线程,然后更新显示以反映任何更改。它执行诸如绘制窗口之类的操作,但不渲染要显示的数据。这为应用程序提供了一种快速的“敏捷性”,这是大多数用户在谈论性能时想要的。他们不

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

于 2009-04-21T15:23:11.457 回答
1

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

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

于 2009-04-21T14:34:53.757 回答
0

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

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

(2) 开始朝着正确的方向前进……尽管每种消息类型的队列都有些极端。我可能会建议您从应用程序中的每个模型的队列开始,然后添加队列以提高性能。

如果您喜欢选项 #2,听起来您会对实现SEDA 架构感兴趣。需要阅读一些内容才能了解正在发生的事情,但我认为该架构非常适合您的思路。

顺便说一句,Yield是一个很好的 C++/Python 混合实现。

于 2009-04-21T18:07:49.983 回答
0

我将有一个线程池为消息队列提供服务,并使池中的线程数易于配置(甚至可能在运行时)。然后用预期的负载对其进行测试。

这样你就可以看到实际的相关性是什么——如果你最初的假设发生了变化,你可以很容易地改变你的方法。

一种更复杂的方法是让系统自省其自身的性能特征并调整其对资源的使用,尤其是线程。对于大多数自定义应用程序代码来说,这可能是矫枉过正,但我​​敢肯定有产品可以做到这一点。

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

于 2009-06-21T22:20:29.343 回答
0

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

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

从某种意义上说,单独的队列提供了更好的控制,您可以按照自己想要的方式对其进行重新排序(可能取决于优先级)

于 2009-04-21T15:27:52.147 回答
0

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

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

这一定是一个后续问题;不是主要问题。第一个问题必须是为什么,而不是如何。

于 2009-04-21T14:32:52.810 回答
0

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

我的 2 美分。

于 2009-04-21T14:33:29.860 回答