【问题标题】:Controlling thread count to balance CPU and IO waits in .NET控制线程数以平衡 .NET 中的 CPU 和 IO 等待
【发布时间】:2011-01-15 04:37:38
【问题描述】:

有没有办法在 .NET 应用程序中确定我当前是否受到 CPU 时间的限制,或者 IO 是否是瓶颈?

我正在查询一堆远程网络服务,其中的细节并不重要,可以抽象为:

// perform a single  operation
if ( [randomness] ) {
    sleep(10s of seconds); // DNS/TCP connection timeout
} else {
    sleep(10s of miliseconds); // query a remote server
}
for x = 1…lots {
    // Do some CPU intensive work
}

虽然我有可用的 CPU,但我希望尽可能多地安排这些线程,因为它们会花费很长时间等待 IO,但是一旦 CPU 完全加载,我不想继续生成线程,因为那样会导致性能崩溃。任务总数为“大”。

一个明显的答案是只选择一个“合理”数量的并发线程作为调整参数,但这会遇到两个问题:

  • “睡眠”的长度在 LAN 往返和 TCP 连接超时之间可能会有很大差异,因此 CPU 工作与 IO 等待时间的比率可能相差 3 个数量级。
  • 将在其上运行的机器的大小可以在小型单 CPU 虚拟机和重型服务器级机器之间变化。

在理想情况下,IO 操作将全部替换为异步完成回调,但在这种情况下这并不容易/不可能,因为网络 RPC 正在使用现有的阻塞代码。

【问题讨论】:

  • 您是否考虑过使用 IO 完成端口?它们与线程本身无关,因此一个线程可以注册这些负载而不会用许多线程污染系统:codeproject.com/KB/cs/managediocp.aspx

标签: c# .net multithreading


【解决方案1】:

对于这种类型的工作,您实际上最好使用异步 i/o 模式,而不是生成一堆线程(或使用大量线程池线程)。

基本上,这涉及使用BeginXxx 方法进行所有I/O 调用,这些调用在等待结果时仅使用I/O 完成端口。当结果返回时,它将触发线程池线程上的回调。最终结果是您的代码只会在 Begin 调用之前和结果返回时运行。您不会有任何线程等待响应。

【讨论】:

  • 那将是“理想”的方式,是的。问题是执行 RPC 的库代码是使用阻塞 IO 编写的,因此我们不能(轻松)重写它以使用非阻塞 IO。
【解决方案2】:

如果您能够使用 .NET 4,则可以使用 Task 并行库。

TPL 将自动为您管理线程数(对于 CPU 密集型操作),或者您可以在需要时限制最大并行度(通常对于您想要限制多少线程的 I/O 密集型操作)立即发生)。

在您的情况下,您可能希望将工作分成两个任务并为每个任务使用单独的自定义TaskScheduler,这样您就可以完全控制分配的线程数以及它们运行的​​ CPU 优先级(例如,将所有计算任务低于正常优先级)。

调度 I/O 任务,然后使用 Task.ContinueWith(请参阅 http://msdn.microsoft.com/en-us/library/dd321307.aspx)将计算部分调度到单独的 TaskScheduler 上。

(这也可能引起一些兴趣:http://mikehadlow.blogspot.com/2010/11/using-task-parallel-library-with-aspnet.html

【讨论】:

    【解决方案3】:

    我推荐两个线程队列,第一个处理 I/O 调用,第二个处理 I/O 后处理,因为您要解决两个不同的问题。

    对于 I/O,我会创建一个线程队列,将 I/O 结果转储到辅助队列中。您可以将大量线程排队并设置活动线程数的限制,使用一个回调,该回调会在线程完成并启动下一个线程时发出信号。您的限制因素将是内存多于 CPU,因为运行线程即使在阻塞时也会保留内存(但尚未启动的线程内存占用量很小)。

    第二个队列可以使用ThreadPool 或您自己的线程的单独集群。通过设置单独的节流阀,您可以根据平均经过时间或类似情况调整两组线程中正在运行的线程数。

    如果您将第二个队列设为数据库表,那么您可以轻松地将工作负载拆分到多台机器(或云)并累积计时统计信息。

    【讨论】:

      【解决方案4】:

      在 C# 中,您可以使用 ThreadPool 来管理所有线程,并对当前使用的线程数设置限制。 .NET 中有一个名为 System.Diagnostics 中的 PerformanceCounter 的对象,它可以帮助您。

      
      PerformanceCounter cpuCounter;
      PerformanceCounter ramCounter;
      
      

      cpuCounter = new PerformanceCounter();

      cpuCounter.CategoryName = "Processor"; cpuCounter.CounterName = "% Processor Time"; cpuCounter.InstanceName = "_Total";

      ramCounter = new PerformanceCounter("Memory", "Available MBytes");

      public string getCurrentCpuUsage(){ cpuCounter.NextValue()+"%"; }

      public string getAvailableRAM(){ ramCounter.NextValue()+"MB"; }

      【讨论】:

      • 抱歉格式错误,在 linux 中从 emacs 复制内容时,由于我的缩进和格式设置方式,我遇到了这个问题。
      猜你喜欢
      • 2011-07-04
      • 2011-06-15
      • 2012-07-21
      • 2021-10-20
      • 1970-01-01
      • 1970-01-01
      • 2010-12-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多