2013-11-01 73 views
1

这里我有一个奇怪的行为,在我的测试代码中,代码很简单,创建了一堆具有相同模式的定时器:在所有回调中添加一个Thread.Sleep。然后几乎同时启动计时器,然后我可以看到一些计时器的回调是延迟Thread.Sleep导致Timer的回调延迟

public class StrangTimerTesting 
{ 
    public int callback_EnteredTimes; 

    // for avoid timers get GCed. 
    private List<System.Timers.Timer> timerContainer = new List<System.Timers.Timer>(); 

    public void Go() 
    { 
     for (var i = 0; i < 5; i++) 
     { 
      var displayTimer = new System.Timers.Timer(1000); 
      displayTimer.Elapsed += (a, b) => 
      { 
       Interlocked.Increment(ref this.callback_EnteredTimes); 
       var initalTime = DateTime.Now.ToString("HH:mm:ss.ffff"); 
       Console.WriteLine("entered times: " + callback_EnteredTimes + ", [email protected]" + initalTime); 
       displayTimer.Stop(); 

       // why this Sleep cause some callback delayed to be called???? 
       Thread.Sleep(6000); 
      }; 

      displayTimer.Start(); 
      timerContainer.Add(displayTimer); 
     } 
    } 
} 

我想所有的回调将被称为几乎相同的时间,虽然在不同的线程池线程,但测试结果显然不支持这一点,有一些必备2秒差距,如果我删除那Thread.Sleep,那么一切都很好。有人可以指出原因吗?

EDIT1:这是从测试程序的结果是:

*进入次数:1,INTIAL @ 08:43:29.4732

进入次数:2,INTIAL @ 08:43:29.4762

进入次数:3,INTIAL @ 08:43:30.4763

进入次数:4,INTIAL @ 08:43:30.9764

进入次数:5,INTIAL @ 08:43:31.4764 *

而且MinThreads在我的笔记本电脑计数为2

+0

总是延迟还是第一次? –

+0

@Sriram:第一2回调始终称为在时间,而后者的总是延迟,这是我的测试结果:进入次数:1,INTIAL @ 16:48:53.6435 进入次数:2,INTIAL @ 16: 48:53.6435 输入次数:3,intial @ 16:48:54.6896 输入次数:4,intial @ 16:48:55.1436 输入次数:5,intial @ 16:48:55.6437 – Shawn

+0

您拥有多少核心?你有没有尝试设置'ThreadPool.SetMinThreads' –

回答

7

您创建了一个firehose问题。你有5个计时器,每个计时器在1秒钟内响起,其Elapsed事件处理器休眠6秒钟。您希望您的程序实际上每秒钟会添加5个线程,需要30秒才能完成工作。换句话说,每分钟增加300个线程,只完成10个线程。

如果放任不管,这不会达到一个好的目的。轻描淡写地说。线程是非常昂贵的操作系统资源。超过5个句柄,它会消耗一个兆字节的虚拟内存。在一个32位的进程中,它只需要7分钟的时间就可以消耗所有可用的内存,并使用OutOfMemoryException异常。

.NET不会让你这样做,而不是不打架。它使用的对策是你观察到的,它不是而是让你开始很多线程。它故意减慢了允许开始的新速率。它只会在你的机器上每秒钟允许2个新线程。

这是线程池调度的工作。它会尝试将执行的TP线程的数量降低到设定的最小值。在你的机器上,最少是2,你拥有的核心数量。让它运行超过2个并没有多大意义,操作系统将不得不做更多的工作来让这些线程有机会运行,并在它们之间进行上下文切换。实际上,减少它们的数量是2,所以它们有一个很好的机会可以无限制地访问处理器并尽快完成他们的工作。

线程池调度但不知道是什么的线程在做什么,它有他们正在执行的代码非常不完善的知识。它不知道你的线程实际上并没有完成任何工作,而只是在睡觉。它有一个对策。两次第二,它会覆盖其默认调度策略,如果活动线程都没有完成,它允许额外线程启动。如果绝对必要,这将继续达到设定的最大允许值。非常高的数字,你的机器应该在500左右。

这是一个非常有效的算法,它会阻止你的程序从7分钟后爆炸。它仍然爆炸,但这需要很长很长的时间。您最终仍然会从OOM中获得TP调度程序无法提供的数百万个TP线程启动请求。否则,只有当你编写不合理的程序时,你总是需要期待一个无理的结果。

+0

感谢ThreadPool的细节,我会澄清一下这个程序,这只是为了测试,Go()只会被调用一次。然后看看我的结果(我只是重新编辑并添加它),第三次输入与第一次和第二次之间有1秒的差距,而第四次与第三次之间的差距为0.5秒,不太可能是您提到的那种“每秒两次”的结果。你能解释一下吗? – Shawn

1

ThreadPool docs on MSDN

当达到最小,线程池可以创建额外的线程或等待某些任务完成。

我怀疑你的线程池的最小值默认为2,因为你的系统有2个内核。该框架将向线程池标识添加线程,但需要做的工作是,但所有线程池线程都处于繁忙状态。然而,正如在文档中提到的那样,框架可能会稍微等待一段时间(并且正在发生这种情况),从而推测正在使用的工作线程很快就会变得可用。

您可以使用ThreadPool.GetMinThreads()方法输出最小线程池数。

+0

似乎你是正确的,但我只是想知道为什么创建一个新的线程池花费一些时间(2秒)? – Shawn