1

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

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 + ", intial@" + 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,初始@08:43:29.4762

输入次数:3,初始@08:43:30.4763

输入次数:4,初始@08:43:30.9764

输入次数:5,初始@08:43:31.4764*

我的笔记本电脑中的 MinThreads 计数为 2。

4

2 回答 2

7

你创造了一个消防水带问题。您有 5 个计时器,每个计时器计时 1 秒,其 Elapsed 事件处理程序休眠 6 秒。您希望您的程序实际上每秒添加 5 个线程,这需要 30 秒才能完成工作。或者换一种说法,它每分钟增加 300 个线程并且只完成其中的 10 个。

如果不加以控制,这将不会有一个好的结局。说得客气一点。线程是非常昂贵的操作系统资源。超过 5 个句柄,它会消耗 1 兆字节的虚拟内存。在 32 位进程中,您的程序只需 7 分钟即可消耗所有可用内存并因 OutOfMemoryException 而崩溃。

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

这是 ThreadPool 调度程序的工作。它试图将执行的 TP 线程数保持在设置的最小值。在您的机器上,最少为 2,即您拥有的核心数。让它运行超过 2 个并没有多大意义,操作系统将不得不做更多的工作来给这些线程一个运行的机会,在它们之间进行上下文切换。实际上减慢了他们的速度,完美的数字是 2,因此他们有很好的机会不受限制地访问处理器并尽快完成他们的工作。

然而,ThreadPool 调度程序不知道线程在做什么,它对它们正在执行的代码的了解非常不完善。它不知道您的线程实际上根本没有完成任何工作,只是在睡觉。它有一个对策。每秒两次,如果活动线程未完成,它会覆盖其默认调度策略,它允许启动额外的线程。如果绝对必要,这将持续到所允许的设置最大值。一个非常高的数字,在您的机器上应该是 500 左右。

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

于 2013-11-01T09:49:38.737 回答
1

MSDN 上的 ThreadPool 文档

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

我怀疑您的线程池最小值默认为 2,因为您的系统有 2 个内核。框架会将线程添加到线程池 id 有工作要做,但所有池线程当前都很忙。然而,正如文档中提到的那样,框架可能会等待一段时间(并且显然正在发生)推测正在使用的工作线程可能很快就会变得可用。

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

于 2013-11-01T08:59:32.470 回答