给出以下代码:
var arrayStrings = new string[1000];
Parallel.ForEach<string>(arrayStrings, someString =>
{
DoSomething(someString);
});
所有1000个线程会几乎同时生成吗?
给出以下代码:
var arrayStrings = new string[1000];
Parallel.ForEach<string>(arrayStrings, someString =>
{
DoSomething(someString);
});
所有1000个线程会几乎同时生成吗?
Answers:
不,它不会启动1000个线程-是的,它将限制使用的线程数。并行扩展会根据您实际拥有的核心数量和已经繁忙的核心数量使用适当数量的核心。它为每个内核分配工作,然后使用一种称为工作窃取的技术来让每个线程有效地处理自己的队列,并且在真正需要时仅需要执行任何昂贵的跨线程访问。
看一看在PFX团队博客的负载有关如何分配工作的信息和各种其他主题。
请注意,在某些情况下,您也可以指定所需的并行度。
在单核计算机上...集合的Parallel.ForEach分区(块)在多个线程之间工作,但是该数量是根据考虑了算法的算法计算出来的,并且似乎不断监视由线程执行的工作。分配给ForEach的线程。因此,如果ForEach的主体部分调用长时间运行的IO绑定/阻塞函数,这会使线程处于等待状态,则该算法将产生更多线程并在它们之间重新分配集合。例如,如果线程快速完成并且不阻塞IO线程,例如仅计算一些数字,该算法将增加(或什至减少)线程数,使算法认为吞吐量最佳(每次迭代的平均完成时间)。
基本上,所有各种Parallel库函数背后的线程池都将确定要使用的最佳线程数。物理处理器内核的数量仅构成等式的一部分。内核数与产生的线程数之间没有简单的一对一关系。
我发现有关取消和处理同步线程的文档没有什么帮助。希望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());
}
现在看看添加等待操作以模拟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)));
}