编辑: 谢谢大家的回答,看起来我只需要测试一下才能找出我的线程计数上限。问题是,我怎么知道我已经达到了这个上限?我应该测量什么?
有些人会说使用两个线程太多了——我不完全赞同 :-)
这是我的建议:多测量,少猜测。一个建议是将线程数设置成可配置的,并最初将其设置为100,然后将软件发布到野外并监视发生的情况。
如果您的线程使用峰值为3,则100太多了。如果它在一天的大部分时间都保持在100个线程上,则可以将其增加到200并观察发生了什么。
你实际上可以让你的代码自行监控使用情况并调整下一次启动的配置,但这可能过于繁琐。
说明和阐述:
我不建议自己编写线程池子系统,尽管可以使用已有的线程池子。但是,既然您询问线程的好截止点,我假设您的线程池实现具有限制创建的最大线程数量的能力(这是一件好事)。
我编写了线程和数据库连接池代码,它们具有以下功能(我认为对性能至关重要):
第一项为线程池客户端设置了最小性能基准线(这些线程始终可用于使用)。第二项对活动线程的资源使用量进行了限制。第三项在静默时间返回您到基线,以最小化资源使用。
您需要平衡具有未使用线程的资源使用(A)与没有足够线程执行工作的资源使用(B)之间的关系。
(A)通常是内存用量(栈等),因为不执行作业的线程不会使用太多的CPU。(B)通常会导致请求处理延迟,因为您需要等待线程变得可用。
这就是为什么你需要进行测量。正如你所说,绝大多数线程都在等待数据库响应时处于空闲状态。有两个因素会影响您应该允许的线程数。
第一个因素是可用的DB连接数。除非您可以在DBMS中增加连接数,否则这可能是一个硬性限制 - 在这种情况下,我将假设您的DBMS可以接受无限数量的连接(尽管您最好也要测量一下)。
其次,您应该根据历史使用情况来确定应该运行的线程数。您应该运行的最小线程数是您曾经启动过的最小线程数加上A%,最低值为5(例如,和A一样可配置)。
线程的最大数量应该是历史最大值加上B%。
您还应该监视行为变化。如果由于某种原因,您的使用率达到了可用量的100%,并持续了相当长的时间(以至于会影响客户端的性能),您应该将允许的最大值提高到再次高出B%。
回复“我应该测量什么?”的问题:
具体应该测量的是负载下并发使用的最大线程数(例如,等待DB调用返回的线程)。然后再增加10%的安全系数(例如)(强调一下,因为其他帖子似乎把我的示例视为固定建议)。
此外,在生产环境中进行优化时需要进行这些测量。预先估计也是可以的,但您永远不知道生产环境会遇到什么问题(这就是为什么所有这些都应该在运行时可配置)。这是为了捕捉诸如客户端调用数量意外翻倍之类的情况。
ThreadPoolExecutor
支持所有三个提到的配置选项,并且可以在运行时进行更改。它的Javadoc还提到了更多的配置权衡(关于排队、负载削减、线程回收等)。 - Dmitry Timofeev这个问题已经被充分讨论过了,我没有机会阅读所有的回复。但是在考虑可以和平共存于给定系统中的同时线程数量上限时,有几件事情需要考虑。
现在,您可以调整堆栈大小以纳入更多线程,但是您必须考虑线程管理(创建/销毁和调度)的开销。您还可以将CPU亲和力强制应用于给定进程以及给定线程,将它们绑定到特定的CPU上,以避免在CPU之间进行线程迁移的开销,并避免冷现金问题。
请注意,一个人可以根据自己的意愿创建成千上万个线程,但当Linux用尽VM时,它只是随机地开始杀死进程(因此线程)。这是为了保持效用曲线不被最大化。(效用函数反映了给定资源量下系统范围内的效用。在这种情况下,CPU周期和内存资源保持不变,随着越来越多的任务,效用曲线逐渐平缓)。如果你的线程在执行任何类型的资源密集型工作(CPU/磁盘),那么你很少会看到超过一两个线程的好处,太多线程会很快降低性能。
“最佳情况”是你后面的线程将在第一个线程完成时停滞不前,或者某些线程在资源竞争较低的资源上具有低开销块。最糟糕的情况是你开始抢占缓存/磁盘/网络,整体吞吐量急剧下降。
一个好的解决方案是将请求放在池中,然后从线程池中分派到工作线程中(避免持续创建/销毁线程是一个很好的第一步)。
这个池中活动线程的数量可以根据您的性能分析结果、运行硬件和其他可能发生在机器上的事情进行调整和扩展。
有一件事情值得注意的是,Python(至少是基于C的版本)使用了被称为全局解释器锁的东西,在多核机器上可能会对性能产生巨大影响。
如果你真的需要最大程度地利用Python的多线程功能,你可能考虑使用Jython或其他语言。
如果不可能,考虑一下您拥有可互换资源(CPU)和不可互换资源(arm)。对于CPU来说,将每个线程分配给特定的CPU并不关键(尽管它有助于缓存管理),但对于arm来说,如果您无法将线程分配给arm,那么就会涉及到排队理论以及保持arm繁忙的最佳数量。一般而言,我认为如果无法根据使用的arm路由请求,则每个arm有2-3个线程左右是最合适的。
当传递给线程的工作单元没有执行合理的原子工作单元时,会出现一种复杂性。例如,您可能在一个时间点上使线程访问硬盘,在另一个时间点上等待网络。这增加了“裂缝”的数量,进而使额外的线程可以进入并执行有用的工作,但同时也增加了额外线程污染彼此的缓存等产生负面影响,从而拖慢系统的运行速度。
当然,您必须衡量所有这些,以便考虑线程的“重量”。很不幸,大多数系统都有非常重的线程(他们称之为“轻量级线程”的东西通常根本不是线程),因此最好保持在较低水平。
实践中我发现微小差别可能会对最优线程数产生巨大影响。特别是,缓存问题和锁冲突可以极大地限制实际并发量。”
需要考虑的一件事是执行代码的机器上有多少个核心。这代表着同时进行的线程数量的硬性限制。然而,如果像你的情况一样,线程经常会等待数据库执行查询,那么你可能需要根据数据库可以处理的并发查询数量来调整线程。
ryeguy,我正在开发一个类似的应用程序,我的线程数设置为15。不幸的是,如果我将它增加到20,它就会崩溃。因此,我认为处理这个问题的最佳方法是测量您当前的配置是否允许更多或更少的X个线程。