更多线程,更好的性能?
Posted
技术标签:
【中文标题】更多线程,更好的性能?【英文标题】:More threads, better performance? 【发布时间】:2010-10-20 20:16:15 【问题描述】:当我编写消息驱动的应用程序时。很像一个标准的 Windows 应用程序,只是它广泛使用消息传递进行内部操作,关于线程的最佳方法是什么?
在我看来,基本上有三种方法(如果您有任何其他设置,请分享):
-
让一个线程处理所有消息。
为不同的消息类型(常规、UI、网络等)拥有不同的线程
拥有多个共享和处理单个消息队列的线程。
那么,这三者之间会存在显着的性能差异吗? 以下是一些一般性的想法: 显然,最后两个选项受益于有多个处理器的情况。另外,如果任何线程正在等待外部事件,其他线程仍然可以处理不相关的消息。但是忽略这一点,似乎多个线程只会增加开销(线程切换,更不用说更复杂的同步情况)。
还有一个问题:您会建议在标准的 Windows 消息系统上实现这样的系统,还是实现单独的队列机制,为什么?
【问题讨论】:
【参考方案1】:线程模型的具体选择应该由您试图解决的问题的性质决定。为这样的应用程序设计线程模型不一定有一种“正确”的方法。但是,如果我们采用以下假设:
-
消息频繁到达
消息是独立的,不要过度依赖共享资源
希望尽快响应到达的消息
您希望应用能够很好地跨处理架构(即多代码/多 CPU 系统)进行扩展
可扩展性是关键的设计要求(例如,更多消息以更快的速度)
对线程故障/长时间操作的恢复是可取的
根据我的经验,最有效的线程架构是使用线程池。所有消息都到达一个队列,多个线程在队列上等待并在消息到达时处理它们。线程池实现可以对您拥有的所有三个线程分布示例进行建模。
#1 单线程处理所有消息 => 只有一个线程的线程池
#2 Thread per N message types => 具有 N 个线程的线程池,每个线程查看队列以查找合适的消息类型
#3 所有消息的多个线程 => 具有多个线程的线程池
这种设计的好处是可以根据处理环境或消息负载成比例地缩放线程中的线程数。线程数甚至可以在运行时扩展,以适应正在经历的实时消息负载。
有很多不错的线程池库可用于大多数平台,包括 .NET、C++/STL、Java 等。
关于你的第二个问题,是否使用标准的windows消息调度机制。这种机制会带来很大的开销,并且实际上仅用于通过 Windows 应用程序的 UI 循环来泵送消息。除非这是您要解决的问题,否则我建议不要将其用作一般的消息发送解决方案。此外,Windows 消息携带的数据非常少——它不是基于对象的模型。每个 Windows 消息都有一个代码和一个 32 位参数。这可能不足以建立一个干净的消息传递模型。最后,windows 消息队列不是为处理队列饱和、线程不足或消息重新排队等情况而设计的;这些是在实施一个体面的消息队列解决方案时经常出现的情况。
【讨论】:
+1:“线程模型的具体选择应该由您要解决的问题的性质决定。”谢谢你。人们似乎不明白这一点,并且倾向于认为如果他们添加线程,应用程序会运行得更快。【参考方案2】:在不知道工作量(即事件随时间的统计分布)的情况下,我们无法肯定地告诉您很多信息,但总的来说
具有多个服务器的单个队列至少一样快,而且通常更快,因此 1,3 比 2 更可取。 大多数语言中的多线程会增加复杂性,因为需要避免争用和多写入器问题 长时间的进程可能会阻止其他可以更快完成的事情的处理。所以马背上的猜测是,有一个事件队列,有几个服务器线程从队列中取出事件,可能会快一点。
确保为队列使用线程安全的数据结构。
【讨论】:
【参考方案3】:这一切都取决于。
例如:
GUI 队列中的事件最好由单个线程完成,因为事件中有一个隐含的顺序,因此它们需要按顺序完成。这就是为什么大多数 GUI 应用程序只有一个线程来处理事件,尽管可能有多个事件来创建它们(并且它并不排除事件线程创建作业并将其处理到工作池(见下文))。
套接字上的事件可能会并行完成(假设 HTTP),因为每个请求都是无状态的,因此可以独立完成(好吧,我知道这过度简化了 HTTP)。
工作作业是每个作业都是独立的并放置在队列中。这是使用一组工作线程的经典案例。每个线程独立于其他线程执行潜在的长时间操作。完成后返回队列等待另一个作业。
【讨论】:
我同意,使用错误模型解决问题的复杂性对性能的影响可能比底层机制的实际效率更大。【参考方案4】:一般来说,不用担心线程的开销。如果您仅谈论其中的一小部分,那将不是问题。竞争条件、死锁和争用是一个更大的问题,如果你不知道我在说什么,在你解决这个问题之前你需要做很多阅读。
我会选择选项 3,使用我选择的语言提供的任何抽象。
【讨论】:
【参考方案5】:请注意,有两个不同的性能目标,您没有说明您的目标是什么:吞吐量和响应能力。
如果您正在编写 GUI 应用程序,则 UI 需要具有响应性。您并不关心每秒可以处理多少次点击,但您确实关心在 10 秒左右(理想情况下更少)内显示一些响应。这是最好有一个专门用于处理 GUI 的线程的原因之一(其他原因已在其他答案中提到)。 GUI 线程需要基本上将 Windows 消息转换为工作项,并让您的工作队列处理繁重的工作。一旦工作人员完成,它会通知 GUI 线程,然后更新显示以反映任何更改。它执行诸如绘制窗口之类的操作,但不渲染要显示的数据。这为应用程序提供了一种快速的“敏捷性”,这是大多数用户在谈论性能时想要的。他们不在乎是否需要 15 秒才能完成一项艰巨的任务,只要他们点击按钮或菜单时,它会立即做出反应。
另一个性能特征是吞吐量。这是您可以在特定时间内处理的作业数量。通常这种类型的性能调整只需要在服务器类型的应用程序或其他重型处理上。这衡量了一个小时内可以提供多少网页,或者渲染一张 DVD 需要多长时间。对于这类作业,您希望每个 CPU 有 1 个活动线程。比这少,你将浪费空闲的时钟周期。不仅如此,线程将争夺 CPU 时间并相互绊倒。查看本文DDJ articles 中的第二张图表,了解您正在处理的权衡。请注意,由于阻塞和锁定等原因,理想的线程数高于可用 CPU 的数量。关键是活动线程的数量。
【讨论】:
【参考方案6】:一个好的开始是问问自己为什么需要多个线程。
对这个问题的深思熟虑的答案将引导您对后续问题“我应该如何在我的应用程序中使用多个线程?”的最佳答案
那一定是一个后续问题;不是主要问题。第一个问题必须是为什么,而不是如何。
【讨论】:
已经表达了担忧——性能。如果他想让线程得到更好的 X,他会问哪个解决方案提供了最好的 X。 如果您不知道具体希望改进什么,那么您最终只是盲目地向您的应用程序抛出线程,希望它能够以某种方式让事情变得更快。但它不是这样工作的,如果你没有真正想要解决的目标或问题,你可以很容易地让你的应用程序运行得更慢并且更容易出现缺陷。【参考方案7】:我认为这取决于每个线程将运行多长时间。每条消息的处理时间是否相同?或者某些消息会花费几秒钟的时间。如果我知道消息 A 需要 10 秒才能完成,我肯定会使用新线程,因为我为什么要为长时间运行的线程保留队列...
我的 2 美分。
【讨论】:
【参考方案8】:我认为选项 2 是最好的。让每个线程执行独立的任务会给你最好的结果。如果多个线程正在执行一些 I/O 操作,如磁盘读取、读取公共套接字等,第三种方法可能会导致更多延迟。
是否使用 Windows 消息传递框架来处理请求取决于每个线程的工作负载。我认为windows限制了no。最多可以排队到 10000 条消息。对于大多数情况,这应该不是问题。但是如果你有很多消息要排队,这可能是需要考虑的事情。
从某种意义上说,单独的队列提供了更好的控制,您可以按照自己的方式对其进行重新排序(可能取决于优先级)
【讨论】:
【参考方案9】:是的,您的选择之间会有性能差异。
(1) 引入了消息处理的瓶颈 (3) 引入了锁定争用,因为您需要同步对共享队列的访问。
(2) 开始朝着正确的方向发展……尽管每种消息类型的队列有点极端。我可能会建议您从应用程序中的每个模型的队列开始,然后添加队列以提高性能。
如果您喜欢选项 #2,听起来您有兴趣实现 SEDA architecture。需要阅读一些内容才能了解正在发生的事情,但我认为该架构非常适合您的思路。
顺便说一句,Yield 是一个很好的 C++/Python 混合实现。
【讨论】:
【参考方案10】:我将有一个线程池为消息队列提供服务,并使池中的线程数易于配置(甚至可能在运行时)。然后用预期的负载对其进行测试。
这样您就可以看到实际的相关性是什么 - 如果您最初的假设发生变化,您可以轻松地改变您的方法。
更复杂的方法是让系统自省其自身的性能特征并调整其对资源的使用,尤其是线程的使用。对于大多数自定义应用程序代码来说,这可能是矫枉过正,但我敢肯定有产品可以做到这一点。
至于 Windows 事件问题 - 我认为这可能是一个特定于应用程序的问题,在一般情况下没有正确或错误的答案。也就是说,我通常实现自己的队列,因为我可以根据手头任务的特定特征对其进行定制。有时这可能涉及通过 Windows 消息队列路由事件。
【讨论】:
以上是关于更多线程,更好的性能?的主要内容,如果未能解决你的问题,请参考以下文章
为了更好的多线程性能,在对象创建或者更新时,若数据大于2047字节则 Python 的 GIL 会被释放。 执行计算密集型任务如压缩或哈希时释放 GIL
为了更好的多线程性能,在对象创建或者更新时,若数据大于2047字节则 Python 的 GIL 会被释放。 执行计算密集型任务如压缩或哈希时释放 GIL