Parallel.ForEach是否限制活动线程的数量?


Answers:


149

不,它不会启动1000个线程-是的,它将限制使用的线程数。并行扩展会根据您实际拥有的核心数量已经繁忙的核心数量使用适当数量的核心。它为每个内核分配工作,然后使用一种称为工作窃取的技术来让每个线程有效地处理自己的队列,并且在真正需要时仅需要执行任何昂贵的跨线程访问。

看一看在PFX团队博客负载有关如何分配工作的信息和各种其他主题。

请注意,在某些情况下,您也可以指定所需的并行度。


2
今晚我使用Parallel.ForEach(FilePathArray,path => ...读取大约24,000个文件,为每个读取的文件创建一个新文件。非常简单的代码。看起来,即使6个线程也足以淹没7200 RPM磁盘我正在以100%的利用率进行读取。在几个小时的时间内,我看到Parallel库剥离了8,000个线程。我使用MaxDegreeOfParallelism进行了测试,并确定足够的8000+线程消失了。结果
杰克·德鲁

可以为一些退化的“ DoSomething”启动1000个线程。(就像在我当前正在处理生产代码中的一个问题那样,该问题未能设置限制并产生了200多个线程,从而弹出了SQL连接池。大约受CPU限制。)
user2864740 '17


28

在单核计算机上...集合的Parallel.ForEach分区(块)在多个线程之间工作,但是该数量是根据考虑了算法的算法计算出来的,并且似乎不断监视由线程执行的工作。分配给ForEach的线程。因此,如果ForEach的主体部分调用长时间运行的IO绑定/阻塞函数,这会使线程处于等待状态,则该算法将产生更多线程并在它们之间重新分配集合。例如,如果线程快速完成并且不阻塞IO线程,例如仅计算一些数字,该算法将增加(或什至减少)线程数,使算法认为吞吐量最佳(每次迭代的平均完成时间)

基本上,所有各种Parallel库函数背后的线程池都将确定要使用的最佳线程数。物理处理器内核的数量仅构成等式的一部分。内核数与产生的线程数之间没有简单的一对一关系。

我发现有关取消和处理同步线程的文档没有什么帮助。希望MS可以在MSDN中提供更好的示例。

别忘了,必须编写主体代码才能在多个线程上运行,以及所有通常的线程安全注意事项,框架尚未抽象该因素……。


1
“ ..如果ForEach的主体部分调用了长时间运行的阻塞函数,这会使线程处于等待状态,则该算法将产生更多线程。”- 在简并的情况下,这意味着可能创建了尽可能多的线程每个ThreadPool。
user2864740

2
您是对的,对于IO,我调试自己时可能会分配+100线程
FindOutIslamNow

5

它根据处理器/内核的数量得出最佳线程数。它们不会一次全部产卵。



4

好问题。在您的示例中,即使在四核处理器上,并行化级别也相当低,但是有些等待,并行化级别可能会很高。

// Max concurrency: 5
[Test]
public void Memory_Operations()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    Parallel.ForEach<string>(arrayStrings, someString =>
    {
        monitor.Add(monitor.Count);
        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

现在看看添加等待操作以模拟HTTP请求时会发生什么。

// Max concurrency: 34
[Test]
public void Waiting_Operations()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    Parallel.ForEach<string>(arrayStrings, someString =>
    {
        monitor.Add(monitor.Count);

        System.Threading.Thread.Sleep(1000);

        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

我还没有做任何更改,并发/并行化的水平急剧上升。并发可以通过以下方式增加其限制ParallelOptions.MaxDegreeOfParallelism

// Max concurrency: 43
[Test]
public void Test()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
    Parallel.ForEach<string>(arrayStrings, options, someString =>
    {
        monitor.Add(monitor.Count);

        System.Threading.Thread.Sleep(1000);

        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

// Max concurrency: 391
[Test]
public void Test()
{
    ConcurrentBag<int> monitor = new ConcurrentBag<int>();
    ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
    var arrayStrings = new string[1000];
    var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
    Parallel.ForEach<string>(arrayStrings, options, someString =>
    {
        monitor.Add(monitor.Count);

        System.Threading.Thread.Sleep(100000);

        monitor.TryTake(out int result);
        monitorOut.Add(result);
    });

    Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}

我建议设置ParallelOptions.MaxDegreeOfParallelism。它不一定会增加正在使用的线程数,但会确保您仅启动合理数量的线程,这似乎是您关心的问题。

最后回答您的问题,不,您不会立即启动所有线程。如果您希望完美地并行调用(例如测试比赛条件),请使用Parallel.Invoke。

// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623368346
// 636462943623368346
// 636462943623373351
// 636462943623393364
// 636462943623393364
[Test]
public void Test()
{
    ConcurrentBag<string> monitor = new ConcurrentBag<string>();
    ConcurrentBag<string> monitorOut = new ConcurrentBag<string>();
    var arrayStrings = new string[1000];
    var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
    Parallel.ForEach<string>(arrayStrings, options, someString =>
    {
        monitor.Add(DateTime.UtcNow.Ticks.ToString());
        monitor.TryTake(out string result);
        monitorOut.Add(result);
    });

    var startTimes = monitorOut.OrderBy(x => x.ToString()).ToList();
    Console.WriteLine(string.Join(Environment.NewLine, startTimes.Take(10)));
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.