OOP Course
Сьогодні

Підрозділ 16.4

Клас Parallel

16.4. Клас Parallel Клас System.Threading.Tasks.Parallel надає зручні статичні методи для паралельного виконання операцій, не вимагаючи явного створення і управління завданнями. Він автоматично розподіляє робот

16.4. Клас Parallel

Клас System.Threading.Tasks.Parallel надає зручні статичні методи для паралельного виконання операцій, не вимагаючи явного створення і управління завданнями. Він автоматично розподіляє роботу між доступними ядрами процесора і чекає завершення всіх паралельних операцій перед поверненням.

На відміну від Task.Run(), де розробник явно описує кожне завдання, Parallel орієнтований на масові паралельні операції: виконання набору незалежних дій, паралельний обхід колекцій, паралельний цикл. Він ефективний, коли є багато однотипних незалежних завдань, що добре розпаралелюються.

Parallel.Invoke — паралельне виконання дій

Parallel.Invoke приймає довільний набір делегатів Action і виконує їх паралельно. Метод повертається тільки після завершення всіх переданих дій:

Parallel.Invoke не гарантує порядку виконання і не гарантує, що кожна дія виконається в окремому потоці — якщо ядер менше, ніж дій, деякі дії будуть почергувати. Але результат завжди правильний: метод поверне управління тільки після завершення всіх.

Parallel.For — паралельний цикл for

Parallel.For(fromInclusive, toExclusive, body) виконує тіло циклу паралельно для всіх індексів у діапазоні [from, to). Планувальник TPL автоматично розбиває діапазон на пакети і виконує їх у потоках пулу:

Зверніть: в одній ітерації використовується Thread.CurrentThread.ManagedThreadId — він показує, що різні ітерації можуть виконуватись у різних потоках пулу.

Parallel.ForEach — паралельний обхід колекції

Parallel.ForEach — аналог Parallel.For, але замість індексів він приймає колекцію IEnumerable<T> і виконує тіло для кожного елемента паралельно:

Метод повертає структуру ParallelLoopResult з двома властивостями:

  • IsCompletedtrue, якщо цикл пройшов до кінця без переривань
  • LowestBreakIteration — індекс, на якому відбулось переривання через Break(), або null

Послідовне vs паралельне виконання

ParallelLoopState: керування циклом

Звичайний break у тілі Parallel.For викличе помилку компілятора — ви не можете використовувати break у лямбді. Натомість Parallel надає об'єкт ParallelLoopState, який передається як другий параметр:

Break() — зупинка після поточного діапазону

pls.Break() сигналізує планувальнику, що після завершення всіх ітерацій з індексом, меншим за поточний, подальші ітерації не потрібні. Це не негайна зупинка — ітерації з вищими індексами, що вже запущені, продовжать виконуватись:

Stop() — негайна зупинка

pls.Stop() вимагає припинити весь цикл якнайшвидше — навіть вже запущені ітерації повинні перевіряти pls.IsStopped і завершуватись достроково. Після Stop() LowestBreakIteration завжди null, а IsCompletedfalse:

Break vs Stop: Break гарантує, що всі ітерації до поточної будуть виконані (корисно для пошуку з гарантованим покриттям). Stop зупиняє якнайшвидше без гарантій покриття (корисно для раннього виходу при знайденні умови).

ParallelOptions: налаштування паралелізму

ParallelOptions дозволяє налаштувати поведінку Parallel.For/ForEach/Invoke. Найважливіша властивість — MaxDegreeOfParallelism: максимальна кількість одночасно виконуваних ітерацій.

MaxDegreeOfParallelism = 1 фактично перетворює паралельний цикл на послідовний — корисно для налагодження. MaxDegreeOfParallelism = -1 (значення за замовчуванням) означає «без обмежень» — TPL сам вирішує.

У реальних системах обмеження паралелізму важливе: якщо кожен потік відкриває з'єднання з базою даних або відправляє мережевий запит, необмежений паралелізм може перевантажити зовнішній ресурс.

Коли Parallel не прискорює

Parallel — не срібна куля. Є сценарії, де паралелізація не допомагає або навіть шкодить:

Занадто короткі ітерації. Якщо тіло циклу займає мікросекунди, накладні витрати на планування задач у TPL (теж мікросекунди) можуть перевищити виграш від паралелізму. Parallel.For є ефективним, коли кожна ітерація займає щонайменше кілька мілісекунд.

Операції із спільним станом без синхронізації. Якщо всі ітерації змінюють одну змінну без lock, виникне race condition. Якщо додати lock — паралелізм де-факто зникне через серіалізацію доступу.

Послідовна природа даних. Якщо кожна ітерація залежить від результату попередньої, паралелізація неможлива — задачу потрібно переосмислити.

Interlocked.Increment — атомарна операція збільшення цілого числа, яка не потребує lock. Для простих лічильників у паралельних циклах це ефективніше за lock (obj) { count++; }.

Розроблено Tomka Yurii · © 2026 ·