我有一些类似下面的代码:
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对抗,以从线程池中获取一个线程来安排回调。
我该如何解决这个问题?
答案 0 :(得分:2)
我似乎遇到了线程池饥饿
饥饿可能是错误的心理模型。线程池调度程序的工作是将执行的tp线程数保持在ThreadPool.SetMinThreads()设置的最小值。默认值等于计算机上可用的处理器核心数。允许更多并发运行通常是有害的,然后操作系统线程调度程序将被强制在它们之间进行上下文切换,这需要时间。
如果这些tp线程实际上刻录了核心,那么这是一个考虑因素。当他们进行“数据库处理”时,这种情况不太可能。这通常涉及大量的死时间,线程等待dbase引擎执行操作。
你的Timer回调也会竞争线程池,它的回调运行在一个tp线程上。如果现有的线程在半秒内没有完成,则tp调度程序允许另一个线程运行。
净效应与您描述的内容有关。你没有看到你的程序使用了很多cpu,并且“clumping”是一个明显的可能性,因为这就是Parallel.ForEach()试图实现的。您希望允许更多 tp线程同时运行,因此至少有一些线程可以执行有意义的工作,而其他线程正在等待dbase引擎。
你是否真的得到它是相当可疑的,很可能它实际上是dbase引擎限制你的程序。接下来是网络带宽。接下来是一个不喜欢你超载他的服务器的滴答dbase管理员。你可以修改ThreadPool.SetMinThreads(),但不要指望奇迹。
请注意,您需要防止计时器回调的重新进入。当现有的Parallel.ForEach()循环没有完成时,让计时器勾选是非常糟糕的。