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

C# Parallel.ForEach是否限制活动线程的数量?,c#,.net,c#-4.0,parallel-processing,C#,.net,C# 4.0,Parallel Processing,鉴于此代码: var arrayStrings = new string[1000]; Parallel.ForEach<string>(arrayStrings, someString => { DoSomething(someString); }); var arrayStrings=新字符串[1000]; Parallel.ForEach(ArrayString,someString=> { DoSomething(someString); }); 所有100

鉴于此代码:

var arrayStrings = new string[1000];
Parallel.ForEach<string>(arrayStrings, someString =>
{
    DoSomething(someString);
});
var arrayStrings=新字符串[1000];
Parallel.ForEach(ArrayString,someString=>
{
DoSomething(someString);
});

所有1000个线程会几乎同时产生吗?

它会根据处理器/内核的数量计算出最佳线程数。它们不会一次全部繁殖。

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

请查看,以获取有关如何分配工作以及各种其他主题的大量信息


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

有关要使用的“心智模型”的想法,请参阅。然而,作者确实指出,“在一天结束时,记住实现细节可能随时发生变化,这一点很重要。”

在单核机器上。。。Parallel.ForEach在多个线程之间处理集合的分区(块),但该数目是基于一个算法计算的,该算法考虑并似乎持续监视分配给ForEach的线程所做的工作。因此,如果ForEach的主体部分调用长时间运行的IO绑定/阻塞函数,这将使线程等待,那么算法将生成更多线程,并在它们之间重新划分集合。例如,如果线程完成得很快,并且没有阻塞IO线程,例如简单地计算一些数字,那么算法将增加(或者实际上减少)线程数,使其达到算法认为吞吐量最佳的点(每个迭代的平均完成时间)

基本上,所有各种并行库函数背后的线程池将计算出要使用的最佳线程数。物理处理器内核的数量只是等式的一部分。核心数量和生成的线程数量之间不存在简单的一对一关系

我不认为有关取消和处理同步线程的文档非常有用。希望MS能够在MSDN中提供更好的示例


不要忘记,主体代码必须编写为在多个线程上运行,以及所有常见的线程安全注意事项,框架不会抽象出该因素。。。然而。

这是一个很好的问题。在您的示例中,即使在四核处理器上,并行化的级别也非常低,但如果等待一些时间,并行化的级别可能会非常高

// 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());
}
我建议设置
ParallelOptions.maxdegreeofpparallelism
。它不一定会增加正在使用的线程数量,但它将确保您只启动一个正常数量的线程,这似乎是您所关心的

最后要回答您的问题,不,您不会一次启动所有线程。使用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)));
}
/636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623368346
// 636462943623368346
// 636462943623373351
// 636462943623393364
// 636462943623393364
[测试]
公开无效测试()
{
ConcurrentBag监视器=新ConcurrentBag();
ConcurrentBag monitorOut=新ConcurrentBag();
var arrayStrings=新字符串[1000];
var options=new ParallelOptions{maxdegreeofpparallelism=int.MaxValue};
Parallel.ForEach(数组字符串、选项、someString=>
{
monitor.Add(DateTime.UtcNow.Ticks.ToString());
monitor.TryTake(输出字符串结果);
监视器输出。添加(结果);
});
var startTimes=monitorOut.OrderBy(x=>x.ToString()).ToList();
Console.WriteLine(string.Join(Environment.NewLine,startTimes.Take(10));
}

我使用的是Parallel.ForEach(FilePathArray,path=>…今晚要读取大约24000个文件,为我读取的每个文件创建一个新文件。代码非常简单。看起来即使是6个线程也足以使我读取的7200 RPM磁盘以100%的利用率被淹没。在几个小时的时间里,我观看了并行库剥离8000多个线程。我使用MaxDegreeOfParallelism,果然8000多个线程消失了。我现在已经测试了多次,结果都是一样的。对于一些退化的“DoSomething”,它可以启动1000个线程。(就像我目前处理的生产代码中的一个问题一样,该问题没有设置限制,产生了200多个线程,从而弹出了SQL连接池。我建议为任何不能简单地解释为显式CPU绑定的工作设置最大DOP。)Partitioner-“。如果ForEach的主体部分调用长时间运行的阻塞函数,这将使线程等待,那么该算法将生成更多线程…”在退化的情况下,这意味着每个线程池可能会创建尽可能多的线程。你是对的,对于IO,它可能会在我自己调试时分配+100个线程
// 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());
}
// 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)));
}