3

我有一些类似于以下的代码:

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 战斗以从线程池中获取一个线程来安排回调。

我该如何解决这个问题?

4

1 回答 1

2

我似乎正在经历线程池饥饿

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

如果这些 tp 线程实际上烧毁了核心,这是一个考虑因素。当他们进行“数据库处理”时,这在您的位置不太可能。这通常涉及大量的死区时间,即等待 dbase 引擎执行操作的线程。

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

最终效果与您所描述的有关。您看不到很多 cpu 正在用于您的程序,并且“聚集”是一种明显的可能性,因为这就是 Parallel.ForEach() 试图实现的目标。您希望允许更多tp 线程同时运行,这样至少会有一些可以做有意义的工作,而其他线程正在等待 dbase 引擎。

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

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

于 2013-07-02T16:49:38.940 回答