Учитывая этот код:
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 потоков появляться почти одновременно?
Ответы:
Нет, он не запустит 1000 потоков - да, он ограничит количество используемых потоков. Parallel Extensions использует соответствующее количество ядер в зависимости от того, сколько у вас физически и сколько уже занято. Он распределяет работу для каждого ядра, а затем использует технику, называемую кражей работы, чтобы позволить каждому потоку эффективно обрабатывать свою собственную очередь и выполнять любой дорогостоящий межпоточный доступ только тогда, когда это действительно необходимо.
Посмотрите на блог PFX Team для нагрузок информации о том , как она распределяет работу и все виды других вопросов.
Обратите внимание, что в некоторых случаях вы также можете указать желаемую степень параллелизма.
На одноядерной машине ... Parallel.ForEach разделы (фрагменты) коллекции, над которыми он работает, между несколькими потоками, но это число рассчитывается на основе алгоритма, который учитывает и, по-видимому, постоянно отслеживает работу, выполняемую потоков, которые он выделяет для ForEach. Поэтому, если часть тела ForEach вызывает длительные функции, связанные с вводом-выводом / блокирующие функции, которые заставят поток ждать, алгоритм создаст больше потоков и перераспределит коллекцию между ними . Если потоки завершаются быстро и не блокируются, например, потоками ввода-вывода, например, просто вычисляя некоторые числа,алгоритм будет увеличивать (или даже уменьшать) количество потоков до точки, в которой алгоритм считает оптимальной для пропускной способности (среднее время завершения каждой итерации) .
По сути, пул потоков, стоящий за всеми различными функциями параллельной библиотеки, будет определять оптимальное количество потоков для использования. Количество физических процессорных ядер составляет лишь часть уравнения. НЕ существует простой взаимосвязи между количеством ядер и количеством порожденных потоков.
Я не считаю документацию по отмене и обработке синхронизирующих потоков очень полезной. Надеюсь, MS сможет предоставить лучшие примеры в MSDN.
Не забывайте, что основной код должен быть написан для работы в нескольких потоках, вместе со всеми обычными соображениями безопасности потоков, фреймворк не абстрагирует этот фактор ... пока.
Он вырабатывает оптимальное количество потоков в зависимости от количества процессоров / ядер. Они не появятся все сразу.
См. Использует ли Parallel.For одну задачу на итерацию? для идеи использования «ментальной модели». Однако автор заявляет: «В конце концов, важно помнить, что детали реализации могут измениться в любое время».
Отличный вопрос. В вашем примере уровень распараллеливания довольно низкий даже на четырехъядерном процессоре, но с некоторым ожиданием уровень распараллеливания может стать довольно высоким.
// 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)));
}