创建多少线程以及何时创建?

Posted

技术标签:

【中文标题】创建多少线程以及何时创建?【英文标题】:How many threads to create and when? 【发布时间】:2009-02-04 07:52:10 【问题描述】:

我有一个网络 Linux 应用程序,它接收来自多个目的地的 RTP 流,进行非常简单的数据包修改,然后将流转发到最终目的地。

我如何决定我应该有多少线程来处理数据?我想,我不能为每个 RTP 流打开一个线程,因为可能有数千个。我应该考虑CPU内核的数量吗?还有什么重要的? 谢谢。

【问题讨论】:

实际上 1000 多个线程可能工作得很好(在 Linux 2.6 下),但可能不是最好的方法 - 请参阅我的回复。 【参考方案1】:

了解在服务器上使用多个线程的目的很重要;服务器中的许多线程用于减少 latency 而不是提高速度。您不会通过拥有更多线程来使 cpu 更快,但您更有可能在给定的时间内总是出现一个线程来处理请求。

拥有一堆只并行移动数据的线程是一个相当低效的霰弹枪(每个请求创建一个线程自然会完全失败)。使用thread pool 模式可能是一种更有效、更集中的方法来减少延迟。

现在,在线程池中,您希望线程数至少与 CPU/内核数一样多。你可以拥有更多,但额外的线程只会减少延迟而不是提高速度。

将组织服务器线程的问题想象成类似于在超市中组织生产线。您想拥有很多工作速度较慢的收银员还是一名工作速度超快的收银员?快速收银员的问题不在于速度,而是一位拥有大量杂货的顾客可能仍会占用大量时间。对许多线程的需求来自于一些请求会花费大量时间并阻塞所有线程的可能性。通过这种推理,您是否受益于许多速度较慢的收银员取决于您是否拥有相同数量的杂货或数量差异很大的杂货。回到基本模型,这意味着你必须使用你的线程数来确定给定流量的特定特征什么是最佳的,查看处理每个请求所花费的时间。

【讨论】:

【参考方案2】:

传统上合理的线程数量取决于执行单元的数量、IO 与计算的比率以及可用内存。

执行单元数 (XU)

计算可以同时活动的线程数。根据您的计算可能会或可能不会计算超线程之类的东西 - 混合指令工作负载会更好地工作。

IO 与计算的比率 (%IO)

如果线程从不等待 IO 但总是计算 (%IO = 0),则使用比 XU 更多的线程只会增加内存压力和上下文切换的开销。如果线程总是等待 IO 并且从不计算 (%IO = 1),那么使用 poll()select() 的变体可能是个好主意。

对于所有其他情况,XU / %IO 给出了完全使用可用 XU 需要多少线程的近似值。

可用内存 (Mem)

这更像是一个上限。每个线程使用一定数量的系统资源(MemUse)。 Mem / MemUse 为您提供系统可以支持多少线程的近似值。

其他因素

即使您可以猜测或(更好地)测量上述数字,整个系统的性能仍然会受到其他因素的限制。例如,系统上可能正在运行另一个服务,它使用一些 XU 和内存。另一个问题是一般可用的 IO 带宽 (IOCap)。如果您需要比 XU 提供的每个传输字节更少的计算资源,显然您不需要完全使用它们,而更关心增加 IO 吞吐量。

有关后一个问题的更多信息,请参阅Google Talk about the Roofline Model。

【讨论】:

【参考方案3】:

我会说,尝试只使用一个线程;它使编程变得更加容易。虽然您需要使用 libevent 之类的东西来多路复用连接,但您不会遇到任何意外的同步问题。

一旦您有了一个有效的单线程实现,您就可以进行性能测试并决定是否需要多线程实现。

即使多线程实现是必要的,如果它们不这样做,将其分解为多个进程而不是线程可能更容易(即不共享地址空间;要么 fork() 要么从父进程执行多个副本)没有很多共享数据。

您还可以考虑使用 Python 的“Twisted”之类的东西来简化实现(这就是它的设计目的)。

确实,在进程上使用线程可能不是一个好案例 - 但也许在您的情况下,很难说。这取决于您需要在线程之间共享多少数据。

【讨论】:

【参考方案4】:

我会研究这个应用程序的线程池。

http://threadpool.sourceforge.net/

允许线程池管理您的线程和队列。

您可以稍后根据性能分析调整使用的最大和最小线程数。

【讨论】:

【参考方案5】:

听取人们建议您使用 libevent(或特定于操作系统的实用程序,例如 epoll/kqueue)的意见。在许多连接的情况下,这是绝对必要的,因为就像你说的那样,创建线程会极大地影响性能,而 select() 也不能完全削减它。

【讨论】:

【参考方案6】:

让您的程序来决定。向其添加代码以测量吞吐量并动态增加/减少线程数以使其最大化。

这样,无论执行核心的数量和其他因素如何,您的应用程序都会始终运行良好

【讨论】:

一个给定的线程池实现可能会自我优化(GNU Make 会这样做),但这肯定不是现状。【参考方案7】:

最好避免尝试为每个客户端请求创建一个(甚至 N 个)线程。这种方法在传统上是不可扩展的,您肯定会遇到内存使用或上下文切换的问题。您应该考虑使用线程池方法,并将传入请求视为池中任何线程要处理的任务。这种方法的可扩展性受到池中理想线程数的限制——通常这与 CPU 内核的数量有关。您想尝试让每个线程在单个内核上准确使用 100% 的 CPU - 所以在理想情况下,每个内核有 1 个线程,这会将上下文切换减少到零。根据任务的性质,这可能是不可能的,也许线程必须等待外部数据,或者从磁盘读取或其他任何东西,所以您可能会发现线程数增加了一些比例因子。

【讨论】:

以上是关于创建多少线程以及何时创建?的主要内容,如果未能解决你的问题,请参考以下文章

多线程使用~会多少?

何时在 C# 中使用线程池? [关闭]

java 线程最多能创建多少个

Java中的堆内存设置对线程创建数的影响以及-Xss参数的记录

nsthread 创建多少条线程

Java启动时默认创建了多少线程