使用与Parallel.ForEach同时运行的Timer防止线程池饥饿

时间:2013-07-02 15:11:07

标签: .net multithreading timer task-parallel-library threadpool

我有一些类似下面的代码:

long progress = 0;

using (Timer timer = new Timer(state => { Console.Write(Interlocked.Read(ref progress); }, null, 5000, 5000)
{
  Parallel.ForEach(list, item =>
  {
     item.DoTask();
     Interlocked.Incrememt(ref progress);
  }
}

我似乎遇到了线程池饥饿,因为写入控制台的数字是偶尔写的 - 当然不是每5秒写一次。在快速连续写入三个数字之前,可能会有15秒的长暂停。我想计时器正在与Parallel.ForEach对抗,以从线程池中获取一个线程来安排回调。

我该如何解决这个问题?

1 个答案:

答案 0 :(得分:2)

  

我似乎遇到了线程池饥饿

饥饿可能是错误的心理模型。线程池调度程序的工作是将执行的tp线程数保持在ThreadPool.SetMinThreads()设置的最小值。默认值等于计算机上可用的处理器核心数。允许更多并发运行通常是有害的,然后操作系统线程调度程序将被强制在它们之间进行上下文切换,这需要时间。

如果这些tp线程实际上刻录了核心,那么这是一个考虑因素。当他们进行“数据库处理”时,这种情况不太可能。这通常涉及大量的死时间,线程等待dbase引擎执行操作。

你的Timer回调也会竞争线程池,它的回调运行在一个tp线程上。如果现有的线程在半秒内没有完成,则tp调度程序允许另一个线程运行。

净效应与您描述的内容有关。你没有看到你的程序使用了很多cpu,并且“clumping”是一个明显的可能性,因为这就是Parallel.ForEach()试图实现的。您希望允许更多 tp线程同时运行,因此至少有一些线程可以执行有意义的工作,而其他线程正在等待dbase引擎。

你是否真的得到它是相当可疑的,很可能它实际上是dbase引擎限制你的程序。接下来是网络带宽。接下来是一个不喜欢你超载他的服务器的滴答dbase管理员。你可以修改ThreadPool.SetMinThreads(),但不要指望奇迹。

请注意,您需要防止计时器回调的重新进入。当现有的Parallel.ForEach()循环没有完成时,让计时器勾选是非常糟糕的。

相关问题