366

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

我的问题是:像这样的 I/O 线程的好的截止点是什么?我知道这只是一个粗略的估计,但我们是在谈论数百个吗?数千?

我将如何弄清楚这个截止点是什么?


编辑:

谢谢大家的回复,看来我只需要对其进行测试以找出我的线程数上限。但问题是:我怎么知道我已经达到了那个天花板?我到底应该测量什么?

4

12 回答 12

252

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

这是我的建议:衡量,不要猜测。一个建议是使其可配置并最初将其设置为 100,然后将您的软件发布到野外并监控会发生什么。

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

实际上,您可以让您的代码本身监控使用情况并在下次启动时调整配置,但这可能有点矫枉过正。


为了澄清和阐述:

我不提倡滚动你自己的线程池子系统,一定要使用你拥有的那个。但是,由于您询问线程的良好截止点,我假设您的线程池实现能够限制创建的最大线程数(这是一件好事)。

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

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

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

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

(A) 通常是内存使用情况(堆栈等),因为不做任何工作的线程不会使用太多 CPU。(B) 通常会在请求到达时延迟处理请求,因为您需要等待线程可用。

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

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

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

最大线程数应该是你的历史最大值 + B%。

您还应该监控行为变化。如果由于某种原因,您的使用量在很长一段时间内达到 100% 可用(这样会影响客户端的性能),您应该提高允许的最大值,直到它再次高出 B%。


回应“我到底应该测量什么?” 问题:

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

此外,这应该在生产环境中进行调整。事先得到一个估计是可以的,但你永远不知道生产会怎样(这就是为什么所有这些东西都应该在运行时可配置的原因)。这是为了捕捉诸如意外加倍的客户端调用的情况。

于 2009-01-27T00:51:10.783 回答
43

这个问题已经讨论得很透彻了,我没有机会阅读所有的回复。但是,在查看可以在给定系统中和平共存的同时线程数的上限时,需要考虑以下几点。

  1. 线程堆栈大小:在 Linux 中,默认线程堆栈大小为 8MB(您可以使用 ulimit -a 来查找)。
  2. 给定操作系统变体支持的最大虚拟内存。Linux Kernel 2.4 支持 2 GB 的内存地址空间。使用 Kernel 2.6 ,我有点大(3GB)
  3. [1] 显示了每个给定的 Max VM Supported 的最大线程数的计算。对于 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/

于 2010-11-18T20:06:04.790 回答
19

如果您的线程正在执行任何类型的资源密集型工作(CPU/磁盘),那么您很少会看到超过一两个的好处,而且太多会很快降低性能。

“最好的情况”是,您后面的线程将在第一个线程完成时停止,或者某些线程在资源争用较低的情况下具有低开销块。最坏的情况是您开始破坏缓存/磁盘/网络,并且您的整体吞吐量下降到最低点。

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

然后可以根据分析结果、正在运行的硬件以及机器上可能发生的其他事情来调整和缩放此池中的活动线程数。

于 2009-01-27T01:03:53.097 回答
12

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

如果您真的需要最大限度地利用多线程 python,您可能需要考虑使用 Jython 或其他东西。

于 2009-09-18T06:01:59.917 回答
9

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

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

Combien de fils d'execution中的完整措施(仅限法语) ?.

于 2009-01-27T08:42:24.997 回答
6

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

换句话说,一旦你遇到瓶颈,比如 CPU、数据库吞吐量、磁盘吞吐量等,添加更多线程不会提高整体性能。但在您达到这一点之前,请添加更多线程!

请注意,这是假设相关系统专用于您的应用程序,并且您不必很好地玩(避免挨饿)其他应用程序。

于 2011-06-08T18:12:39.880 回答
4

“大铁”的答案通常是每个有限资源一个线程——处理器(CPU 绑定)、arm(I/O 绑定)等——但只有当你可以将工作路由到正确的线程以获取资源时,它才有效被访问。

在不可能的情况下,请考虑您拥有可替代资源 (CPU) 和不可替代资源 (arm)。对于 CPU,将每个线程分配给特定的 CPU 并不重要(尽管它有助于缓存管理),但对于 arm,如果您不能为 arm 分配线程,您就会进入排队理论以及保持 arm 的最佳数量是多少忙碌的。一般来说,我认为如果你不能根据使用的手臂路由请求,那么每个手臂有 2-3 个线程将是正确的。

当传递给线程的工作单元没有执行合理的原子工作单元时,就会出现复杂情况。例如,您可能让线程在某一时刻访问磁盘,而在另一时刻在网络上等待。这增加了额外线程可以进入并做有用工作的“裂缝”数量,但它也增加了额外线程污染彼此缓存等的机会,并使系统陷入困境。

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

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

于 2012-01-15T15:13:15.893 回答
3

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

于 2009-01-27T00:51:42.730 回答
3

我认为这对您的问题有点回避,但是为什么不将它们分叉到进程中呢?我对网络的理解(从以前的朦胧日子开始,我根本不编写网络代码)是每个传入的连接都可以作为一个单独的进程处理,因为如果有人在你的进程中做了一些讨厌的事情,它不会核对整个程序。

于 2009-01-27T00:55:16.547 回答
2

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

于 2009-01-27T12:36:16.600 回答
-6

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

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

于 2009-01-27T00:53:37.797 回答
-12

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

于 2009-01-27T00:48:45.437 回答