鉴于此代码:
var arrayStrings = new string[1000];
Parallel.ForEach<string>(arrayStrings, someString =>
{
DoSomething(someString);
});
几乎所有1000个线程都会同时产生吗?
答案 0 :(得分:139)
不,它不会启动1000个线程 - 是的,它将限制使用的线程数。 Parallel Extensions使用适当数量的内核,具体取决于您实际拥有的内核数量和已经忙碌的数量。它为每个核心分配工作,然后使用一种称为工作窃取的技术,让每个线程有效地处理自己的队列,只需要在需要时进行任何昂贵的跨线程访问。
查看PFX Team Blog 加载有关如何分配工作和各种其他主题的信息。
请注意,在某些情况下,您也可以指定所需的并行度。
答案 1 :(得分:26)
在单个核心机器上...... Parallel.ForEach分区(块)它正在多个线程之间进行处理,但是这个数字是根据一个算法来计算的,该算法考虑到并且似乎在不断地监视工作由它分配给ForEach的线程完成。所以如果ForEach的主体部分调用长时间运行的IO绑定/阻塞函数,这会使线程等待,算法将产生更多线程并在它们之间重新分配。例如,如果线程快速完成并且不在IO线程上阻塞,例如简单地计算一些数字,则算法将使线程数增加(或实际上减少)到算法认为对吞吐量最佳的点。 (每次迭代的平均完成时间)。
基本上,所有各种并行库函数背后的线程池将计算出最佳的线程数量。物理处理器核心的数量只是等式的一部分。核心数量与产生的线程数量之间没有简单的一对一关系。
我没有找到有关取消和处理同步线程的文档非常有用。希望MS可以在MSDN中提供更好的示例。
不要忘记,必须编写正文代码以在多个线程上运行,以及所有常见的线程安全注意事项,框架不会抽象那个因素......但是。
答案 2 :(得分:5)
根据处理器/核心数量计算出最佳线程数。它们不会一下子全部产卵。
答案 3 :(得分:5)
请参阅Does Parallel.For use one Task per iteration?了解要使用的“心理模型”。然而,作者确实声明“在一天结束时,重要的是要记住实施细节可能随时发生变化。”
答案 4 :(得分:3)
好问题。在您的示例中,即使在四核处理器上,并行化水平也相当低,但有些等待并行化水平可能会非常高。
// 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)));
}