使用多少个线程?

14

我知道已经有一些现有的问题并且它们提供了非常好的总体观点。 我希望获得关于某些观点的 C#/VB.Net 方面的细节,而不是哲学层面上的实际实施。

我的特定情况

我有一个 WCF 服务,其中包括接收文件等功能。 在服务的大部分生命周期中,这个特定领域实际上只是闲置的 - 当工作真正来临时,会以极高的数量迅速到达。

对于每个接收到的文件(最多可以达到每秒数千个),服务需要为每个文件工作1-10秒钟(从其他服务,本地资源和网络 IO 等待时间等方面取决于多种因素)。

为了帮助服务处理这些突发负载,我实施了队列系统。 每秒收到的成千上万个文件被放置在队列上。 控制器根据队列大小计算要使用的线程数,直到达到“峰值最大线程”设置,这将防止创建额外的线程。 这些线程被放置在线程池中,并重新用于循环遍历队列。 控制器将定期重新计算所需的线程数。 如果队列的大小减少,则释放相应数量的线程。

老生常谈的问题

我应该将线程峰值设置为多少? 显然,每次接收到文件时添加一个新线程是愚蠢的 - 性能最好将恶化。 当 CPU 利用率仅为每个核心的 10% 时限制线程似乎也不是最佳资源使用方式。

那么,有没有适当的方法来确定要限制的线程数? 我希望服务可以通过抽样可用资源来自行确定这一点,但这样做是否会影响性能? 我知道通常的答案是监视工作负载,通过试错调整计数,直到找到我喜欢的数字,但由于这项服务的性质(长时间闲置后跟随高/突发工作量),这可能需要很长时间才能获得这种信息。

如果我们将服务器的镜像移动到一个不同于之前速度更快/更慢/不同的主机上,那该怎么办?我是否需要重新进行所有过程的重采样?

理想情况下,我希望协调员能够智能地增加线程池的大小,直到CPU利用率达到x%(80%是否合理?90%?99%?)。显然,我希望在不添加超过必要数量的线程以达到x%的情况下实现这一点,否则我将得到的只是线程不仅在等待IO资源,而且互相等待。

提前致谢!


相关问题(如果您想获得一些通用的想法):

创建多少个线程?

线程数量过多?

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


一个让你复杂化的问题

如果问题不复杂,那会有多少乐趣呢?

目前为止,服务在这些突发情况下确实会达到100%的CPU使用率。问题在于CPU利用率会突然激增,从空闲状态(0-10%)达到100%,然后再次下降。我不确定我是否可以解决这个问题——理想情况下,我不想将其一直提高到100%。该问题的原因在于所提到的文件实际上是图片,而服务的一部分过程是将图像传递给System.Windows.Media黑盒子,它会为我执行一些复杂的图像处理。

由于I/O等待和其他处理,CPU利用率会不断地出现波峰后又有平静期。如果无法避免 CPU 利用率达到 100%(我很想知道如何预防这种情况,或者是否有必要),应该让 CPU 利用率图形呈现什么样的状态?始终保持在 100%?在 50-100% 之间反复跳动?如果我尝试取样来决定哪种方法效果最好,那么更改虚拟服务器的主机是否可以保证获得相同的图表效果?

对于愿意回答的人,我不需要考虑这个额外的复杂性。请随意忽略此部分内容。然而,任何带有这种复杂性的回答,或者提供处理建议的回答,我至少会点赞!

非常冗长的问题 - 对此表示抱歉 - 感谢您的耐心阅读!!


CPU利用率达到80-85%可能是最佳状态。良好的使用,但保留一些资源来处理后台任务(例如运行服务接口)。 - Schroedingers Cat
@Schroedingers - 我同意这个观点。更高的要求只会显得“自私”,就像你所说的,即使它完全是以自我为中心的,我所做的只是减缓服务前端的速度。感谢你指出这一点。 - Smudge202
@Schroedingers 当然可以 - 我无法防止CPU超过给定的百分比。如果超过了,我应该采取什么措施?我需要测量它是否一直超过吗? - Smudge202
那听起来非常有趣。让我去找一些关于这个主题的链接 - 谢谢@Martin - Smudge202
@Smudge 希望你能使用下面 @Marinos 的答案。如果你需要手动操作,我建议只有在当前水平低于阈值时才开始新文件 - 鉴于你的波动情况,可能在更低的水平上,比如80%左右。你仍然会偶尔达到100%,但它应该提供整体良好的利用率。 - Schroedingers Cat
显示剩余4条评论
3个回答

6

PerformanceCounter 允许您查询处理器使用情况。

但是,您尝试过框架提供的其他东西吗?

        foreach (var file in files)
        {
            var workitem = file;
            Task.Factory.StartNew(() =>
            {
                // do work on workitem
            }, TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
        }

你可以调整工厂中任务的并发级别。
默认情况下,.NET 4线程池会在运行的硬件上调度它发现的最高效线程数,但是你可以使用上面提到的链接来更改它的工作方式。
可能你需要一个定制的解决方案,但测试标准方案也是可以的。
编辑:(评论注释)
没有链接需要,我可能使用了一个发明的术语,因为英语不是我的语言。我的意思是:有一个变量,你可以在上一次检查之前存储方差(prevDelta),并将其称为delta。每次“检查”时,将其添加到变量avrageDelta中并除以2。您将拥有变量averageDelta,它大多数情况下会很低,因为您没有活动。然后有另一组delta变量,其中一个已经存在(delta - prevdelta),并将其存储在不是所有delta平均值而是小时间跨度内的delta平均值的delta变量中(您必须想出一个算法来准确计算这种时间方差)。完成后,您可以比较平均delta和“临时delta”。平均delta大多数情况下会很低,并且会慢慢上升,当突发事件发生时会快速上升。在同一时期,临时delta将非常快地上升。然后您就有了突发停止的情况,平均delta会慢慢下降,而“临时”则会非常快速。

+1 我有一种感觉,这个框架应该内置了处理至少部分问题的功能,所以感谢您向我展示它的位置!您认为 Task 工厂单独能够处理我的特定情况,还是只用于基准测试?有没有一种方法可以测量工厂正在使用的线程池的相关细节,以便确定如何调整我的自定义实现? - Smudge202
如果您按照第二个链接构建调度程序,它可以处理。其余部分只是在循环中生成任务,并指示它们是否长时间运行以及是否需要公平性(执行顺序)。 - Marino Šimić
1
@Cicada 是的,但它将使用所有可用的CPU,要求是尝试在突发期间平均使用高达80%。 - Marino Šimić
1
@smudge:我看到你对问题的补充了……CPU峰值会一直发生,因为CPU利用率是一个定时平均值。您必须计算这些突发持续多长时间,并在这些突发期间尝试保持80%的平均CPU利用率。 - Marino Šimić
这一点有下降的趋势-目前控制器是从队列中工作的。 队列由前端添加。 如果我使用queue.ToList().ForEach(x => 或等效项,它不会考虑新添加到队列中的项目。 有最佳处理方法吗? - Smudge202
显示剩余5条评论

2
您可以使用I/O完成端口来异步获取图像,而不会占用任何线程,直到处理已获取的内容时才会占用线程。
然后,您可以根据客户端PC上核心的数量限制线程池,确保留出一个核心供其他进程使用。

谢谢你的建议@mbkeckish - 我会认真阅读并回复你。 - Smudge202

0
如何考虑一个动态线程管理器,它可以监控线程的整体性能,并根据此生成新线程或终止旧线程?主要问题在于如何定义性能测量函数。其余部分可以通过定期调度的作业来完成,该作业根据先前的线程数和性能增加或减少线程数量,或者类似于这样的操作。也许还可以与资源利用(CPU、磁盘、网络等)相关联。

1
我认为真正的挑战在于如何访问性能损失。可以使用什么来获取当前CPU使用率,然后用于监视并将其保持在80%? - Schroedingers Cat
正如@Schroedingers所说的那样 - 我同意这听起来原则上是个好主意@ gw0(尽管我即将在问题中添加一些细微的复杂性,所以请稍等)。 问题是,怎么做? (我认为这就是防止这成为我链接的问题的重复的原因)。 有人做过这个并且愿意分享片段和知识吗? =) - Smudge202

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接