问题描述
当我在我的程序中使用Parallel.ForEach,我发现某些线程似乎从来没有结束。事实上,它不停地一遍又一遍产生新的线程,我没想到,绝对不希望的行为。
While I was using Parallel.ForEach in my program, I found that some threads never seemed to finish. In fact, it kept spawning new threads over and over, a behaviour that I wasn't expecting and definitely don't want.
我能够重现此问题有以下code的,就像我的真正的程序,无论使用的处理器和内存的大量(.NET 4.0 code):
I was able to reproduce this behaviour with the following code which, just like my 'real' program, both uses processor and memory a lot (.NET 4.0 code):
public class Node
{
public Node Previous { get; private set; }
public Node(Node previous)
{
Previous = previous;
}
}
public class Program
{
public static void Main(string[] args)
{
DateTime startMoment = DateTime.Now;
int concurrentThreads = 0;
var jobs = Enumerable.Range(0, 2000);
Parallel.ForEach(jobs, delegate(int jobNr)
{
Interlocked.Increment(ref concurrentThreads);
int heavyness = jobNr % 9;
//Give the processor and the garbage collector something to do...
List<Node> nodes = new List<Node>();
Node current = null;
for (int y = 0; y < 1024 * 1024 * heavyness; y++)
{
current = new Node(current);
nodes.Add(current);
}
TimeSpan elapsed = DateTime.Now - startMoment;
int threadsRemaining = Interlocked.Decrement(ref concurrentThreads);
Console.WriteLine("[{0:mm\\:ss}] Job {1,4} complete. {2} threads remaining.", elapsed, jobNr, threadsRemaining);
});
}
}
在我的四核运行,它最初开始的4个并发线程,就像你期望的那样。然而,随着时间的推移越来越多线程被创建。最终,这个程序就抛出一个OutOfMemoryException:
When run on my quad-core, it initially starts of with 4 concurrent threads, just as you would expect. However, over time more and more threads are being created. Eventually, this program then throws an OutOfMemoryException:
[00:00] Job 0 complete. 3 threads remaining.
[00:01] Job 1 complete. 4 threads remaining.
[00:01] Job 2 complete. 4 threads remaining.
[00:02] Job 3 complete. 4 threads remaining.
[00:05] Job 9 complete. 5 threads remaining.
[00:05] Job 4 complete. 5 threads remaining.
[00:05] Job 5 complete. 5 threads remaining.
[00:05] Job 10 complete. 5 threads remaining.
[00:08] Job 11 complete. 5 threads remaining.
[00:08] Job 6 complete. 5 threads remaining.
...
[00:55] Job 67 complete. 7 threads remaining.
[00:56] Job 81 complete. 8 threads remaining.
...
[01:54] Job 107 complete. 11 threads remaining.
[02:00] Job 121 complete. 12 threads remaining.
..
[02:55] Job 115 complete. 19 threads remaining.
[03:02] Job 166 complete. 21 threads remaining.
...
[03:41] Job 113 complete. 28 threads remaining.
<OutOfMemoryException>
的内存使用图表对上面实验如下:
The memory usage graph for the experiment above is as follows:
(的截图是在荷兰;顶部再presents处理器的使用,底部的内存使用的),正如你所看到的,它看起来像一个新的线程正在催生几乎每个垃圾收集得到的方式(如可在内存使用的逢低可以看出)。
(The screenshot is in Dutch; the top part represents processor usage, the bottom part memory usage.) As you can see, it looks like a new thread is being spawned almost every time the garbage collector gets in the way (as can be seen in the dips of memory usage).
任何人都可以解释为什么发生这种情况,而且我能做些什么呢?我只想.NET停止产生新的线程,并首先完成现有的线程...
Can anyone explain why this is happening, and what I can do about it? I just want .NET to stop spawning new threads, and finish the existing threads first...
推荐答案
您可以限制得到通过指定一个 ParallelOptions
实例创建的最大线程数 MaxDegreeOfParallelism
属性设置:
You can limit the maximum number of threads that get created by specifying a ParallelOptions
instance with the MaxDegreeOfParallelism
property set:
var jobs = Enumerable.Range(0, 2000);
ParallelOptions po = new ParallelOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount
};
Parallel.ForEach(jobs, po, jobNr =>
{
// ...
});
至于为什么的你得到你观察的行为:TPL (其中underlies PLINQ),默认情况下,随意猜测的最佳数量线程使用。每当并行任务块中,任务调度器可以以保持进步创建新线程。在你的情况下,阻止可能发生的含蓄;例如,通过 Console.WriteLine
电话,或(如你观察到的)垃圾收集过程中。
As to why you're getting the behaviour you're observing: The TPL is, by default, at liberty to guess the optimal number of threads to use. Whenever a parallel task blocks, the task scheduler may create a new thread in order to maintain progress. In your case, the blocking might be happening implicitly; for example, through the Console.WriteLine
call, or (as you observed) during garbage collection.
由于第三方物流默认策略是使用每个处理器一个线程,我们可以得出结论,TPL最初假定任务的工作量〜100%的工作和0%的等待,如果最初的假设不成立,任务进入等待状态(即开始阻塞) - TPL与冒昧地增加线程适当
这篇关于Parallel.ForEach不断产生新的线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!