2015-10-04 68 views
4

出于测试目的,我编写CPU压力程序:它只是在M个线程中执行N个for-loops。 我用大量的线程运行这个程序,比如说200. 但是在任务管理器中我看到线程计数器没有超过一些小值,比如说9和一个Thread.Start方法等待完成以前的运行线程为什么Thread.Start方法在CPU负载过高时被阻塞?

这种行为看起来像一个ThreadPool行为,但我期望定期System.Threading.Thread必须启动无需等待某种原因。下面

代码将重现这一问题,并有解决方法的选择:

using System; 
using System.Diagnostics; 
using System.Threading; 

namespace HeavyLoad 
{ 
    class Program 
    { 
     static long s_loopsPerThread; 
     static ManualResetEvent s_startFlag; 

     static void Main(string[] args) 
     { 
      long totalLoops = (long)5e10; 
      int threadsCount = 200; 

      s_loopsPerThread = totalLoops/threadsCount; 

      Thread[] threads = new Thread[threadsCount]; 

      var watch = Stopwatch.StartNew(); 
      for (int i = 0; i < threadsCount; i++) 
      { 
       Thread t = new Thread(IntensiveWork); 
       t.IsBackground = true; 
       threads[i] = t; 
      } 
      watch.Stop(); 
      Console.WriteLine("Creating took {0} ms", watch.ElapsedMilliseconds); 

      // *** Comment out s_startFlag creation to change the behavior *** 
      // s_startFlag = new ManualResetEvent(false); 

      watch = Stopwatch.StartNew(); 
      foreach (var thread in threads) 
      { 
       thread.Start(); 
      } 
      watch.Stop(); 
      Console.WriteLine("Starting took {0} ms", watch.ElapsedMilliseconds); 

      if (s_startFlag != null) 
       s_startFlag.Set(); 

      watch = Stopwatch.StartNew(); 
      foreach (var thread in threads) 
      { 
       thread.Join(); 
      } 
      watch.Stop(); 
      Console.WriteLine("Waiting took {0} ms", watch.ElapsedMilliseconds); 

      Console.ReadLine(); 
     } 

     private static void IntensiveWork() 
     { 
      if (s_startFlag != null) 
       s_startFlag.WaitOne(); 

      for (long i = 0; i < s_loopsPerThread; i++) 
      { 
       // hot point 
      } 
     } 
    } 
} 

案例1:如果s_startFlag创作的评论,然后启动线程立即开始高密集的CPU工作。在这种情况下,我有一个小的并发(约9个线程)和所有的时间我抱上线启动代码:

Creating took 0 ms 
Starting took 4891 ms 
Waiting took 63 ms 

案例2:但如果我创建s_startFlag,所有新的线程将等待,直到它会被设置。在这种情况下,我成功地启动所有200个线程同时并获得预期值:一点时间开始和很多时间线程在任务管理器中的工作,号码是200 +:

Creating took 0 ms 
Starting took 27 ms 
Waiting took 4733 ms 

为什么线程垃圾开始第一种情况?我超过了什么样的限制?

系统:

  • 操作系统:Windows 7专业版
  • 框架:NET 4.6
  • CPU:英特尔酷四核Q9550 @ 2.83GHz
  • RAM:8千兆
+0

可能你正在寻找一个硬件/操作系统限制?例如,磁盘I/O肯定会有最大通道限制,这会导致一些线程被阻塞...... – code4life

+0

此测试不涉及任何磁盘I/O。这看起来像限制“你有CPU负载95%,所以我不启动任何线程”。但我没有听说过这种限制。为什么会发生? –

+0

问题是,究竟是什么导致CPU负载? CPU正在做一些事情,这就是仪器告诉你的。运行一个分析器在这一点上是非常有意义的。 – code4life

回答

1

我做了一些研究,现在我发现高CPU负载对线程启动时间确实有很大的影响。

第一:为了有更多的观察时间,我设置totalLoops为100倍大的值。我看到线程不受限制,但非常缓慢地创建。 1个线程在1-2秒内启动!第二:我明确地将一个主线程绑定到CPU核心#0,并且使用SetThreadAffinityMask函数(https://sites.google.com/site/dotburger/threading/setthreadaffinitymask-1)将线程工作到核心#1,#2,#3。

Stopwatch watch; 
using (ProcessorAffinity.BeginAffinity(0)) 
{ 
    watch = Stopwatch.StartNew(); 
    for (int i = 0; i < threadsCount; i++) 
    { 
     Thread t = new Thread(IntensiveWork); 
     t.IsBackground = true; 
     threads[i] = t; 
    } 
    watch.Stop(); 
    Console.WriteLine("Creating took {0} ms", watch.ElapsedMilliseconds); 
} 

using (ProcessorAffinity.BeginAffinity(1, 2, 3)) 
{ 
    for (long i = 0; i < s_loopsPerThread; i++) 
    { 
    } 
} 

现在主线程拥有自己的专用CPU核心(在这个过程中的边界)和工作线程之后开始〜10毫秒(totalLoops = 5E10)。

Creating took 0 ms 
Starting took 2282 ms 
Waiting took 3681 ms 

此外,我发现这句话在MSDN:

当你调用线程。在线程上启动方法,该线程可能为 或可能不会立即开始执行,具体取决于处理器的数量和当前等待执行的线程数。

https://msdn.microsoft.com/en-us/library/1c9txz50(v=vs.110).aspx

结论: Thread.Start方法非常积极的工作线程数量敏感。这可能是一个非常强大的性能影响 - 数百倍的放缓。

相关问题