为什么C#中的多线程无法达到100% CPU利用率?

30

我正在开发一款处理多个请求的程序,但没有一个请求占用超过50%的CPU(目前我正在使用双核)。因此,我为每个请求创建了一个线程,整个处理过程更快。处理9个请求时,单个线程持续时间为02min08s,而同时工作3个线程的时间减少到01min37s,但仍然没有使用100%的CPU,只使用了大约50%。

我该如何让我的程序充分利用处理器的能力?

编辑 这个应用程序不受IO或内存限制,它们始终保持在合理的水平。

我认为这与“双核”有关。

有一个锁定的方法调用,每个请求都使用它,但它非常快,我认为这不是问题所在。

我代码中成本更高的部分是通过COM调用dll(所有线程都调用相同的外部方法)。这个dll也没有内存或IO限制,它是一个AI识别组件,我正在对一份支票进行OCR识别,每个请求对应一份支票。

第二次编辑

很可能STA COM方法是我的问题,我联系了组件所有者以解决这个问题。

13个回答

25

你的应用程序中是否有显著的锁定?如果线程之间相互等待很多,那可能很容易解释。

除此之外(和其他已给出的答案),真的很难猜测。分析器是你的朋友...

编辑:好的,考虑到下面的评论,我想我们正在做某些事情:

我代码中较耗费CPU的部分是通过COM调用dll(所有线程都调用同一个外部方法)。

这个 COM 方法是否在STA中运行?如果是,则只使用一个线程串行调用。我强烈怀疑这是关键。这类似于在该方法调用周围有一个锁(诚然不完全相同)。


有一个被锁定的方法调用,每个请求都使用它,但它非常快,我不认为这是问题所在。 - Victor Rodrigues
我的代码中CPU成本更高的部分是通过COM调用DLL(所有线程都调用相同的外部方法)。该DLL也不受内存或IO限制。 - Victor Rodrigues
不,你可以拥有可以从多个线程(MTA)调用的COM对象。 - mackenir
我赞同STA的诊断。听起来非常有可能。 - Stu Mackellar
很不幸,我不认识Jon,事实上这是我第一次通过COM从一个.NET项目访问非.NET Dll。我该如何检查/更改它? - Victor Rodrigues
1
老实说,我不太清楚你该如何检查它 - 首先尝试在资源管理器中查看属性。至于更改它 - 你不能;如果它被设计为STA,则更改可能是不安全的。你需要询问原始作者。 - Jon Skeet

17
问题出在COM对象上。
大多数COM对象在“单线程公寓”的上下文中运行。(你可能会偶尔在.NET应用程序的主方法上看到[STAThread]注释?)
实际上,这意味着该对象的所有分派都由一个线程处理。将更多的核心投入问题只会给您更多的资源,可以在.NET中等待或执行其他任务。
您可能希望查看Microsoft首席并行.NET专家Joe Duffy在此主题上的文章。
请参阅此文章:http://www.bluebytesoftware.com/blog/PermaLink,guid,8c2fed10-75b2-416b-aabc-c18ce8fe2ed4.aspx 实际上,如果您必须针对此类单个COM对象执行一堆操作,则会遇到麻烦,因为.NET会在背后序列化访问模式。如果您可以创建多个COM对象并使用它们,则可以解决此问题,因为每个对象都可以从不同的STA线程创建和访问。这将起作用,直到达到大约100个STA线程,然后事情就会变得混乱。有关详细信息,请参见文章。

这是我在一些旧的PDF库中发现的一件令人愉悦的事情之一。 - StingyJack
这也是为什么你不敢在Web服务器上调用各种Excel.Application或Office Web组件的原因。突然间,当你跨越100个线程时,它们开始翻转并共享全局变量,并从错误的线程中摧毁彼此等。 - Edward Kmett

13

处理器可能不再是完成您的流程的瓶颈。瓶颈很可能已经转移到了磁盘访问、网络访问或内存访问。您也可能会遇到线程竞争锁的情况。

只有您确切地知道您的线程在做什么,因此您需要根据上述内容来查看它们。


4

这取决于您的程序功能 - 您并发请求所执行的工作可能是IO限制的 - 受到(例如)硬盘速度的限制,而不是CPU限制,此时您会看到CPU占用率达到100%。

编辑后,听起来COM STA对象可能是罪魁祸首。

所有线程是否调用相同的COM对象实例?是否可能将您的工作线程设置为STA线程,并在每个线程上创建单独的COM对象实例。通过这种方式,可能可以避免STA瓶颈。

要确定COM coclass是否为STA:

class Test
{
  static void Main() //This will be an MTA thread by default
  {
    var o = new COMObjectClass();
    // Did a new thread pop into existence when that line was executed?
    // If so, .NET created an STA thread for it to live in.
  }
}

是的,很混乱,它们都在调用同一个实例,我会尝试为每个线程创建一个实例,谢谢。 - Victor Rodrigues
我尝试为每个线程加载一个实例,结果导致了IO绑定的情况。 - Victor Rodrigues
在这个更改之前,运行需要大约2分钟,而现在需要超过3分钟。 - Victor Rodrigues
取决于该COM对象的功能,我想。 - mackenir

2

我认为我遇到了类似的问题。我在C#中创建了多个线程,通过COM接口运行C++代码。我的双核CPU从未达到100%。

阅读了这篇文章后,我几乎放弃了。然后我尝试在我的线程上调用SetApartmentState(ApartmentState.STA)。

仅仅更改这一点,CPU就达到了最大值。


0

听起来你的应用程序性能可能不会受到可用 CPU 资源的“限制”。如果你正在通过网络处理请求,则 CPU 可能在等待数据到达或者等待网络设备传输数据。或者,如果你需要查找数据来满足请求,则 CPU 可能在等待磁盘。


0

你确定你的任务需要大量的处理器活动吗?有任何IO处理吗?这可能是你50%负载的原因。

测试: 尝试只使用2个线程,并为每个核心设置每个线程的亲和力。然后打开任务管理器,观察两个核心的负载。


只有非常少量的IO处理,仅有几KB。 - Victor Rodrigues
我猜我错了 :( 没有托管代码可以做到这一点,而我找到的非托管代码似乎存在问题。抱歉。 - bruno conde

0
这并不是一个真正的答案,但你是否已经检查了perfmon以查看它正在使用哪些资源,并且是否已经在代码上运行了分析器以查看它花费时间的位置?
您如何确定IO或其他非CPU资源不是瓶颈?
您能简要描述一下线程正在做什么吗?

0

如果您的进程在 CPU 0 上运行并在那里生成线程,则其最大值将达到50%。请查看是否在两个核心或仅一个核心上运行了线程。我敢说您只限于单个核心,或者其中一个依赖资源被锁定在单个核心上。如果它恰好达到50%,则很可能是单个核心成为瓶颈。


我在这个项目的主线程上有一个旧代码,它使得整个项目占用了大约100%的处理资源,代码里包含一个while-true条件。当然,我已经修复了这个代码,因为它会消耗过多的资源并且是错误的,但同时也表明了这个项目可以以100%运行。 - Victor Rodrigues

0

所以你解决了使用单个COM对象的问题,现在又遇到了IO问题。

多线程的运行时间增加可能是因为混合了随机IO,这会使所有操作变慢。

如果数据集可以放入RAM中,请尝试将其预取到缓存中。也许只需读取数据,或者将其与命令一起内存映射以使其可用。

这就是为什么SQL数据库通常会选择顺序表扫描而不是索引扫描查询,即按顺序读取所有内容比随机读取块要快得多。


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