Ответ
Parallel — это класс в пространстве имён System.Threading.Tasks, предоставляющий упрощённый API для параллельного выполнения циклов (for, foreach) и независимых действий над коллекциями данных. Он абстрагирует ручное управление потоками, используя пул потоков .NET.
Основные методы:
Parallel.For— параллельный аналог циклаfor.Parallel.ForEach— параллельный аналог циклаforeach.Parallel.Invoke— параллельное выполнение набора действий (Action).
Когда использовать? Идеально для CPU-bound задач, где есть большой объём однотипных вычислений, которые можно выполнять независимо (например, обработка пикселей изображения, симуляции, математические расчёты).
Пример: параллельная обработка коллекции.
using System.Threading.Tasks;
var data = Enumerable.Range(1, 10000).ToList();
var results = new double[data.Count];
// Последовательная обработка
foreach (var item in data)
{
results[item - 1] = PerformHeavyCalculation(item);
}
// Параллельная обработка с Parallel.For
Parallel.For(0, data.Count, i =>
{
// Каждая итерация может выполняться в отдельном потоке
results[i] = PerformHeavyCalculation(data[i]);
});
// Параллельная обработка с Parallel.ForEach
Parallel.ForEach(data, (item, state, index) =>
{
results[index] = PerformHeavyCalculation(item);
});
static double PerformHeavyCalculation(int input)
{
// Имитация ресурсоёмкой операции
return Math.Sqrt(Math.Pow(input, 3)) * Math.Tan(input);
}
Критически важные моменты:
- Потокобезопасность: Тело цикла (
Action) выполняется в нескольких потоках одновременно. Доступ к общим ресурсам (коллекциям, статическим полям) должен быть синхронизирован (например, с помощьюlock,ConcurrentBag,Interlocked).var sharedSum = 0; object lockObj = new object(); Parallel.For(0, 1000, i => { // НЕПРАВИЛЬНО: sharedSum += i; // Race condition! // ПРАВИЛЬНО: lock (lockObj) { sharedSum += i; } // ИЛИ ЛУЧШЕ (для простых операций): Interlocked.Add(ref sharedSum, i); }); - Порядок выполнения: Итерации выполняются не по порядку. Нельзя полагаться на последовательность.
- Неподходящие сценарии: Для I/O-bound операций (чтение файлов, сетевые запросы)
Parallelблокирует потоки пула. Вместо этого используйте асинхронное программирование (Task.WhenAll,Parallel.ForEachAsyncв .NET 6+). - Накладные расходы: Для очень маленьких коллекций или простых операций оверхед на распараллеливание может превысить выгоду. Всегда замеряйте производительность.
Ответ 18+ 🔞
Смотри, есть такая штука в .NET — Parallel. Это, по сути, обёртка, чтобы не ебаться с потоками вручную, когда нужно тупо нагрузить все ядра процессора какой-нибудь однотипной хуйнёй. Типа циклов for или foreach, но чтобы они бежали одновременно, а не друг за другом.
Что там есть по методам:
Parallel.For— это как обычныйfor, только параллельный.Parallel.ForEach— ну, ты понял, для коллекций.Parallel.Invoke— когда нужно запустить кучу независимых действий разом.
Когда это впихивать? Идеально, когда у тебя CPU-bound задача — то есть процессор грузится, а не ждёт диск или сеть. Например, пересчёт пикселей в картинке, какие-то тяжёлые математические вычисления на массивах данных — вот тут самое оно.
Вот тебе пример, как это выглядит в коде:
using System.Threading.Tasks;
var data = Enumerable.Range(1, 10000).ToList();
var results = new double[data.Count];
// По-старинке, в один поток (скучно и медленно, если операция тяжёлая)
foreach (var item in data)
{
results[item - 1] = PerformHeavyCalculation(item);
}
// А теперь по-взрослому, с Parallel.For
Parallel.For(0, data.Count, i =>
{
// Каждая итерация может прилететь в свой поток
results[i] = PerformHeavyCalculation(data[i]);
});
// Или через ForEach, если так удобнее
Parallel.ForEach(data, (item, state, index) =>
{
results[index] = PerformHeavyCalculation(item);
});
static double PerformHeavyCalculation(int input)
{
// Представь тут какую-нибудь ебучую формулу
return Math.Sqrt(Math.Pow(input, 3)) * Math.Tan(input);
}
А теперь, блядь, самое важное, что надо помнить, а то наебнёшься:
- Потокобезопасность — это всё. Тело цикла выполняется в нескольких потоках сразу. Если лезешь без спросу в общую переменную или коллекцию — получишь race condition и неконсистентные данные. Используй
lock, потокобезопасные коллекции (ConcurrentBag) илиInterlockedдля простых операций.var sharedSum = 0; object lockObj = new object(); Parallel.For(0, 1000, i => { // ТАК НЕЛЬЗЯ: sharedSum += i; // Будет полная хуйня, данные потекут! // ТАК НАДО: lock (lockObj) { sharedSum += i; } // ИЛИ ТАК (для атомарных операций): Interlocked.Add(ref sharedSum, i); }); - Порядок — не гарантирован. Итерации начнут и закончат выполняться в произвольном порядке. Не строй логику, которая от этого зависит.
- Не для всего подходит. Если твоя задача — качать файлы из интернета или читать с диска (I/O-bound), то
Parallelбудет создавать кучу потоков, которые просто будут висеть и ждать. Это тупо и неэффективно. Для такого есть асинхронщина (Task.WhenAll,Parallel.ForEachAsyncв .NET 6+). - Накладные расходы. Если твоя коллекция — три элемента, а операция внутри — сложение двух чисел, то оверхед на распараллеливание сожрёт всю выгоду. Для мелких задач это пустая трата времени. Всегда проверяй производительность, не верь на слово.