多少线程太多?


312

我正在编写服务器,并且在收到请求时将每个动作发送到一个单独的线程中。我这样做是因为几乎每个请求都会进行数据库查询。我正在使用线程池库来减少线程的构造/销毁。

我的问题是:这样的I / O线程的最佳切入点是什么?我知道这只是一个粗略的估计,但是我们正在讨论数百个吗?几千

我将如何确定这个临界值?


编辑:

谢谢大家的答复,看来我只是必须对其进行测试才能确定我的线程数上限。但问题是:我怎么知道我已经达到那个上限了?我到底应该测量什么?


1
@ryeguy:此处的全部要点是,如果开始时没有性能问题,则不应在线程池中设置任何最大值。将线程池限制为约100个线程的大多数建议都是荒谬的,大多数线程池的/ way /线程数都超过该数量,并且从来没有问题。
GEOCHET

ryeguy,请参阅以下我的答案中有关测量的内容。
paxdiablo

不要忘记Python是天生的,并不是真正的多线程友好。在任何时间点,都将执行单个字节码操作码。这是因为Python使用了Global Interpreter Lock。
ASk

1
@Jay D:我想说的是,当您达到极限时,就是您的演奏开始下降。
ninjalj

6
@GEOCHET “最重要的是,您不应在线程池中设置任何最大值”嗯...说什么?固定大小的线程池具有适度降级和可伸缩性的优点。例如,在网络设置中,如果您基于客户端连接生成新线程,而没有固定的池大小,则可能会冒着真正的危险(很难)学习服务器可以处理多少个线程以及每个连接的客户端会受苦的。固定大小的池的作用类似于管道阀,它使服务器无法试图咬下去而无法咀嚼。
b1nary.atr0phy

Answers:


206

有人会说两个线程太多了-我不在那个阵营里:-)

这是我的建议:衡量,不要猜测。一种建议是使其可配置,并将其初始设置为100,然后将您的软件发布并监视发生的情况。

如果您的线程使用量达到3的峰值,那么100太多了。如果在一天的大部分时间里保持在100,将其提高到200,然后看看会发生什么。

实际上,您可以让代码本身监视使用情况,并在下次启动时调整配置,但这可能太过分了。


为了澄清和阐述:

我不主张滚动自己的线程池子系统,请务必使用现有的线程池子系统。但是,由于您询问的是线程的一个好起点,因此我假设您的线程池实现能够限制创建的最大线程数(这是一件好事)。

我已经编写了线程和数据库连接池代码,它们具有以下功能(我认为这对于性能至关重要):

  • 最少活动线程数。
  • 最大线程数。
  • 关闭一段时间未使用的线程。

第一个为线程池客户端设置了最低性能的基准(此数量的线程始终可用)。第二个方法设置了活动线程对资源使用的限制。第三个使您在安静的时候回到基线,以最大程度地减少资源使用。

您需要在没有使用线程(A)的资源使用与没有足够的线程来完成工作(B)的资源使用之间取得平衡。

(A)通常是内存使用情况(堆栈等),因为不执行任何操作的线程不会占用大量CPU。(B)通常会延迟请求的处理时间,因为您需要等待线程可用。

这就是为什么要测量。如您所说,您的绝大多数线程将等待数据库的响应,因此它们将不会运行。有两个因素影响应允许的线程数。

第一个是可用的数据库连接数。除非您可以在DBMS上增加它,否则这可能是一个硬性限制-在这种情况下,我将假设您的DBMS可以进行无限数量的连接(尽管理想情况下您也应该进行测量)。

然后,您应该拥有的线程数取决于您的历史使用情况。您应该运行的最小值是您曾经运行的最小值+ A%,绝对最小值为(例如,使其像A一样可配置)5。

最大线程数应为您的历史最大值+ B%。

您还应该监视行为更改。如果由于某种原因,您的使用率在相当长的一段时间内达到可用状态的100%(这样会影响客户端的性能),则应提高允许的最大值,直到再次提高B%。


回应“我应该精确测量什么?” 题:

您应该具体衡量的是负载下并发使用(例如,等待DB调用返回)的最大线程数。然后添加例如 10%的安全系数(强调,因为其他张贴者似乎以我的示例为固定建议)。

另外,这应该在生产环境中进行调整。可以事先获得估算值,但是您永远不知道哪种生产方式会影响您的生产(这就是为什么所有这些东西都应该在运行时可配置的原因)。这是为了应对意外情况,例如传入的客户端呼叫意外加倍。


如果在传入的请求上生成线程,则线程使用将镜像未服务请求的数量。无法从中确定“最佳”数字。实际上,您会发现更多的线程导致更多的资源争用,因此活动线程的数量将增加。
安德鲁·格兰特

@Andrew,线程创建需要时间,您可以根据历史数据[+ N%]确定最佳数目(因此,不要猜测)。另外,更多线程仅在工作时才引起资源争用,而不等待信号/信号量。
paxdiablo

使用线程池时,“线程创建”中的这些数据在哪里导致性能问题?一个好的线程池不会在任务之间创建和销毁线程。
GEOCHET

@Pax如果所有线程都在等待相同的信号量以运行数据库查询,那么这就是争用的定义。说线程在等待信号量不花任何钱也不是真的。
Andrew Grant

1
@Andrew,我看不到为什么要用信号量阻止数据库查询,任何体面的数据库都将允许并发访问,并且有许多线程在等待响应。并且在信号阻塞期间,线程不应该花费任何执行时间,它们应该坐在阻塞队列中,直到释放信号为止。
paxdiablo

36

这个问题已经进行了非常彻底的讨论,我没有机会阅读所有答案。但是,在考虑可以在给定系统中和平共存的并发线程数的上限时,需要考虑以下几件事。

  1. 线程堆栈大小:在Linux中,默认线程堆栈大小为8MB(您可以使用ulimit -a找出来)。
  2. 给定的OS变体支持的最大虚拟内存。Linux Kernel 2.4支持2 GB的内存地址空间。使用Kernel 2.6时,我要大一点(3GB)
  3. [1]显示了每个给定“支持的最大VM”的最大线程数的计算。对于2.4,结果约为255个线程。对于2.6,这个数字要大一些。
  4. 您拥有什么样的内核调度程序。将Linux 2.4内核调度程序与2.6进行比较,后者提供的O(1)调度与系统中现有任务的数量无关,而第一个任务更多的是O(n)。因此,内核调度的SMP功能在系统中最大数量的可持续线程中也起着很好的作用。

现在,您可以调整堆栈大小以合并更多线程,但随后必须考虑线程管理(创建/销毁和调度)的开销。您可以将CPU Affinity强制应用于给定的进程以及给定的线程,以将它们绑定到特定的CPU,以避免CPU之间的线程迁移开销并避免冷钱问题。

请注意,一个人可以随意创建数千个线程,但是当Linux用完VM时,它只是随机地开始杀死进程(因此线程)。这是为了防止实用程序配置文件被最大化。(效用函数说明给定资源量的系统范围的效用。在这种情况下,如果使用恒定的资源CPU周期和内存,则效用曲线会随着越来越多的任务而趋于平坦)。

我确定Windows内核调度程序也可以进行此类操作以应对资源的过度利用

[1] http://adywicaksono.wordpress.com/2007/07/10/i-can-not-create-more-than-255-threads-on-linux-what-is-the-solutions/


17

如果您的线程正在执行任何类型的资源密集型工作(CPU /磁盘),那么您几乎看不到一两个以上的好处,太多的好处将很快导致性能下降。

“最佳情况”是,您的后面的线程将在第一个线程完成时停止运行,或者某些线程在资源上的开销较小且争用程度较低。最坏的情况是您开始破坏缓存/磁盘/网络,并且总体吞吐量下降。

一个好的解决方案是将请求放置在一个池中,然后将其从线程池分派给工作线程(是的,避免连续创建/销毁线程是一个重要的第一步)。

然后,可以根据分析的结果,运行的硬件以及计算机上可能发生的其他情况,来调整和扩展该池中活动线程的数量并进行缩放。


是的,它应与队列或请求池一起使用。
安德鲁·格兰特

2
@安德鲁:为什么?每次收到请求时,都应将任务添加到线程池中。当任务可用时,由线程池为任务分配线程。
GEOCHET

那么,当有成百上千个请求进入线程外而又没有线程时,您该怎么办?创建更多?块?返回错误?将您的请求放入可能需要的大小的池中,然后在线程空闲时将这些排队的请求提供给线程池。
安德鲁·格兰特

“创建了多个线程来执行许多任务,这些任务通常组织在队列中。通常,任务比线程多得多。线程一旦完成其任务,就会从队列中请求下一个任务直到所有任务都完成为止。”
GEOCHET

@Andrew:我不知道OP正在使用什么蟒蛇线程池,但是如果你想要这个功能我描述一个真实的例子:msdn.microsoft.com/en-us/library/...
GEOCHET

10

您应该记住的一件事是,python(至少是基于C的版本)使用了所谓的全局解释器锁,该可能会对多核计算机的性能产生巨大影响。

如果您真的最需要多线程python,则可以考虑使用Jython或其他工具。


4
阅读此内容后,我尝试在三个线程上运行Eratosthenes任务的筛子。果然,它实际上比在单个线程中运行相同任务 50%。感谢您的注意。我在分配了两个CPU的虚拟机上运行Eclipse Pydev。接下来,我将尝试一个涉及一些数据库调用的方案。
唐·柯比

3
有两种(至少)任务类型:CPU绑定(例如,图像处理)和I / O绑定(例如,从网络下载)。显然,GIL“问题”不会对I / O绑定任务产生太大影响。如果您的任务受CPU限制,则应考虑多处理而不是多线程。
iutinvg

1
是的,如果您有很多网络io,python线程就会有所改进。我将其更改为线程,并且比普通代码快了10倍...
tyan

8

就像Pax正确说的那样,测量,不要猜测。我为DNSwitness所做的工作和结果令人惊讶:理想的线程数比我想象的要高得多,大约15,000个线程才能获得最快的结果。

当然,这取决于很多事情,这就是为什么您必须衡量自己。

执行全面措施(仅法语)


1
15,000?这也比我预期的要高。不过,如果那是您得到的,那就是您得到的,我不能否认这一点。
paxdiablo

2
对于此特定应用程序,大多数线程只是在等待来自DNS服务器的响应。因此,挂钟时间越多,并行性越好。
bortzmeyer

18
我认为,如果您有15000个线程在某些外部I / O上阻塞,那么更好的解决方案将是大大减少线程,但使用异步模型。我是根据经验讲的。
史蒂夫

5

我已经编写了许多高度多线程的应用程序。我通常允许由配置文件指定潜在线程的数量。当我针对特定客户进行调优时,我将数字设置得足够高,以至于我对所有CPU内核的利用率都很高,但又没有那么高,以至于我遇到了内存问题(这些是32位操作系统)时间)。

换句话说,一旦遇到CPU,数据库吞吐量,磁盘吞吐量等瓶颈,添加更多线程将不会提高整体性能。但是直到达到该点为止,添加更多线程!

请注意,这假设相关系统专用于您的应用程序,并且您不必表现出色(避免挨饿)其他应用程序。


1
您能否提及一些您看到的线程数数字?对此有所了解会很有帮助。谢谢。
kovac

3

“大铁”的答案通常是每个有限资源(处理器(与CPU绑定),臂(与I / O绑定)等)只有一个线程,但是只有在您可以将工作路由到资源的正确线程时才能起作用。被访问。

在不可能的情况下,请考虑您拥有可替代的资源(CPU)和不可替代的资源(武器)。对于CPU而言,将每个线程分配给特定的CPU并不是至关重要的(尽管它有助于缓存管理),但是对于Arm而言,如果您不能为Arm分配线程,则会进入排队理论以及保持Arm的最佳数量忙。通常,我在想,如果您不能基于使用的分支路由请求,那么每个分支有2-3个线程将是正确的。

当传递给线程的工作单元无法执行合理的原子工作单元时,就会出现复杂问题。例如,您可能让线程在某一点访问磁盘,而在另一点等待网络。这增加了其他线程可以进入并完成有用工作的“裂缝”的数量,但同时也增加了其他线程污染彼此的缓存等并使系统瘫痪的机会。

当然,您必须权衡所有这些对线程的“重量”。不幸的是,大多数系统都有非常重量级的线程(它们所谓的“轻量级线程”通常根本不是线程),因此最好在低端进行。

我在实践中看到的是,非常细微的差异可以使最佳线程数产生巨大差异。特别是,缓存问题和锁冲突会大大限制实际的并发量。


2

要考虑的一件事是机器上将要执行代码的内核数。这表示在任何给定时间可以处理多少个线程的硬限制。但是,如果像您的情况那样,预计线程会频繁地等待数据库执行查询,则您可能希望根据数据库可以处理的并发查询数来调整线程。


2
不。线程的全部目的是(在多核和多个处理器流行之前)能够模仿在一台只有一个的机器上拥有多个处理器。这就是您获得响应性用户界面的方式-主线程和辅助线程。
mmr

1
@mmr:嗯。线程的思想是允许阻塞I / O和其他任务。
GEOCHET

4
我所做的陈述是,一台计算机上的内核数量代表了在给定时间可以进行工作的线程数量的硬限制,这是事实。当然,其他线程可能正在等待I / O操作完成,因此这个问题是一个重要的考虑因素。
newdayrising

1
无论如何-您在Python中拥有GIL,这使得线程在理论上仅是并行的。最多可以同时运行1个线程,因此重要的只是响应性和阻塞操作。
Abgan

2
+1用于实际了解计算机的工作方式。@mmr:您需要了解似乎有多个处理器和确实有多个处理器之间的区别。@Rich B:线程池只是处理线程集合的多种方法之一。这是一个好人,但肯定不是唯一的一个。
悲伤

2

我认为这对您的问题有些疑惑,但是为什么不将它们分叉到流程中呢?我对网络的理解(从过去的朦胧时代开始,我根本就不编码网络)是,每个传入的连接都可以作为一个单独的过程来处理,因为如果有人在您的过程中做了一些令人讨厌的事情,核对整个程序。


1
对于Python来说尤其如此,因为多个进程可以并行运行,而多个线程却不能并行运行。但是成本很高。您每次必须启动新的Python解释器,并通过每个进程连接到DB(或使用一些管道重定向,但这也是有代价的)。
Abgan

在大多数情况下,在进程之间切换比在线程之间切换(整个上下文切换而不是某些寄存器)要昂贵。最后,它在很大程度上取决于您的线程库。由于问题围绕线程展开,因此我认为流程已经毫无疑问了。
Leonidas

很公平。我不知道为什么这就是为什么我的分数是-2,除非人们真的想看到仅线程的答案,而不是包括其他可行的答案。
mmr

@mmr:考虑到问题是关于/ thread /池,是的,我认为人们应该期待有关线程的答案。
GEOCHET

进程创建可以在启动时完成一次(即,进程池而不是线程池)。在申请期限内摊销,这可能很小。他们无法轻松共享信息,但是确实为他们提供了在多CPU上运行的可能性,因此此答案很有用。+1。
paxdiablo

1

ryeguy,我目前正在开发一个类似的应用程序,并且我的线程数设置为15。不幸的是,如果我将其增加到20,它将崩溃。因此,是的,我认为处理此问题的最佳方法是测量您当前的配置是否允许多于或少于X个线程。


5
添加到线程数中不应使应用程序随机崩溃。是有原因的 知道的话,您会做得很好,因为即使在某些情况下使用更少的线程,它也可能会影响您。
马修·隆德

-6

在大多数情况下,应允许线程池处理此问题。如果您发布一些代码或提供更多详细信息,则可能会更容易查看是否有某种原因导致线程池的默认行为不是最佳选择。

您可以在此处找到有关其工作方式的更多信息:http : //en.wikipedia.org/wiki/Thread_pool_pattern


1
@Pax:这不是大多数人第一次不想回答眼前的问题(或理解它)。我不担心
GEOCHET

-10

我经常听到与CPU内核一样多的线程。


5
@Rich,至少要解释原因:-)。此经验法则仅适用于所有线程都受CPU约束的情况。他们每个获得一个“ CPU”。当许多线程受I / O约束时,通常最好拥有比“ CPU”更多的线程(引用CPU是因为它适用于执行的物理线程,例如核心)。
paxdiablo

1
@Abgan,我不确定这一点,认为Python可能会创建“真正的” OS线程(在多个CPU上运行)。如果您说的是正确的(我没有理由怀疑),则CPU数量没有影响-线程仅在大多数线程正在等待某些内容(例如DB I / O)时才有用。
paxdiablo

1
@Rich :(真正的)线程时,CPU计数确实有影响,因为您可以真正地同时运行多个非等待线程。使用一个CPU时,只有一个CPU运行,并且受益于让许多其他线程等待非CPU资源。
paxdiablo

1
@Pax:那我想您不了解线程池的概念。
GEOCHET

1
@Rich,我知道线程池很好;看来我(和这里的其他人)也比您更了解硬件。一个CPU只能运行一个执行线程,即使还有其他线程正在等待一个CPU。两个CPU,两个可以运行。如果所有线程都在等待CPU,则理想的线程数等于...
paxdiablo
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.