Высокоуровневый параллелизм
•Минусы низкоуровневые средств
•Параллелизм задач
•Передача данных между задачами
•Обработка исключений
•Параллелизм данных
•PLINQ
•Потокобезопасные коллекции
Минусы низкоуровневых средств
Трата ресурсов
операционной
системы
Много
инфраструктурного
кода
Много переделок
по сравнению с
последовательной
версией
Много нюансов, не
имеющих
отношения к
прикладной задаче
Много проблем с
синхронизацией
Опускание уровня
языка до
ассемблера
Высокоуровневые средства
• Средства распараллеливания
• Средства синхронизации
• Средства отмены выполнения
• Структуры данных
Параллелизм
задач
• Параллельные циклы
• Параллельные запросы
Параллелизм
данных
Распараллеливание
• new Thread(() => DoGetData(startDate, endDate, requestId, config, jumperings,
receiverUri)) {IsBackground = true }.Start();
• Потребляет ресурсы операционной системы
• Стартует после завершения создания потока
• Не учитывает конфигурацию среды исполнения
• Много шумового кода
Было
• Parallel.Invoke(() =>
DoGetData(startDate, endDate, requestId, config, jumperings, receiverUri));
• Не потребляет дополнительных ресурсов, используя общий планировщик
задач
• Стартует без дополнительных пауз, если очередь задач пуста
• Учитывает количество логических процессоров в системе
• Лаконичный синтаксис
Стало
Синхронизация
• var endRead = new AutoResetEvent(false);
• Надо явно создавать системные примитивы синхронизации
• new Thread(() => { action(endRead); }.Start();
• Явное создание потока с передачей примитива
• if(!WaitHandle.WaitAll(new[] {endRead, EndWrite}, ExecutionTimeout))
• Ожидание события
• Снова много инфраструктурного кода, расход ресурсов, задержки
Было
• var readTask = Task.TaskFactory.StartNew(() => { action(); });
• Создание и запуск логической задачи – потоками занимается планировщик
• if (!Task.WaitAll(new[] {readTask, sendTask}, ExecutionTimeout))
• Ожидание окончания выполнения задачи
• Код более лаконичный, прозрачный, декларативный, системные ресурсы
сам по себе не потребляет
Стало
Передача данных между задачами
•var pipe = new BlockingCollection<TrendValue>(PortionSize);
•Размер порции влияет только на количество блокировок и переключений задач (чем
больше порция – тем меньше)
•И для источника и для приемника канал бесконечен, хотя потребляет конечный
(ограниченный размером порции) объем памяти
Создание канала
•pipe.Add(value);
•И так можно делать сколь угодно много раз
•pipe.CompleteAdding();
•Если данных больше нет и не будет – это надо указать явно
Запись в канал
задачей- источником
•foreach (var value in buffer.GetConsumingEnumerable())
•Для приемника весь поток данных предстает в виде одной большой последовательности
•Закончится последовательность только если (и когда) источник подтвердит отсутствие
данных
Чтение из канала
задачей-приемником
•С использованием специальных структур данных получается очень лаконичный ,
прозрачный , нетребовательный к ресурсам код, позволяющий параллельно выполнять
длительные конвеерные операции
Резюме
Обработка исключений
• Необработанное исключение убьет процесс
Исключения в
задачах не теряются
• Task.WaitAll(readTask, sendTask);
• Если хотя бы одна из задач завершилась с
исключением, то оно будет выброшено в этой строке
Исключения
пробрасываются в
место ожидания
задачи
• Если планируется перехват исключений более чем от
одной задачи, то обязательно надо ловить
AggregateException
Исключения от
нескольких задач
группируются в
AggregateException
Отмена выполнения
• var cts = CancellationTokenSource();
Создание источника
отмены
• var token = cts.Token;
• Task.TaskFactory.StartNew(() => action(token), token);
Передача токена
задаче
• cts.Cancel();Отмена задачи
• token.ThrowIfCancellationRequested();
Проверка отмены в
задаче
• Исключение OperationCancelledException
Результат
отмененной задачи
Параллелизм данных
•Было: foreach (var item in SourceCollection) process(item);
•Стало: Parallel.ForEach(sourceCollection, item => { process(item); });
•Отмена: Parallel.ForEach(enumerable, new
ParallelOptions(){CancellationToken = cts.Token}, item =>
{process(item);});
foreach
•Было: for(var i = 1; i < 10; i++) process(i);
•Стало: Parallel.For(0, 10, i => {process(i);});for
PLINQ
•foreach(var item in from i in enumerable.AsParallel() select
compute(i))Просто
•foreach(var item in from i in enumerable.AsParallel().AsOrdered()
select compute(i))С сохранением порядка
•foreach(var item in from i in
enumerable.AsParallel.WithCancellation(cts.Token) select compute(i))С возможностью отмены
•(from i in enumerable.AsParallel() select compute(i)).ForAll(item =>
{action(item);})
С параллельной
обработкой результатов
•Надо ловить AggregateExceptionОбработка исключений
Потокобезопасные коллекции
• var dictionary = new
Dictionary<TKey, TValue>();
• lock(_locker){dictionary[key] =
value;}
Было
• var dictionary = new
ConcurrentDictionary<TKey, TValue>
();
• Dictionary[key] = value;
Стало

Высокоуровневый параллелизм

  • 1.
    Высокоуровневый параллелизм •Минусы низкоуровневыесредств •Параллелизм задач •Передача данных между задачами •Обработка исключений •Параллелизм данных •PLINQ •Потокобезопасные коллекции
  • 2.
    Минусы низкоуровневых средств Тратаресурсов операционной системы Много инфраструктурного кода Много переделок по сравнению с последовательной версией Много нюансов, не имеющих отношения к прикладной задаче Много проблем с синхронизацией Опускание уровня языка до ассемблера
  • 3.
    Высокоуровневые средства • Средствараспараллеливания • Средства синхронизации • Средства отмены выполнения • Структуры данных Параллелизм задач • Параллельные циклы • Параллельные запросы Параллелизм данных
  • 4.
    Распараллеливание • new Thread(()=> DoGetData(startDate, endDate, requestId, config, jumperings, receiverUri)) {IsBackground = true }.Start(); • Потребляет ресурсы операционной системы • Стартует после завершения создания потока • Не учитывает конфигурацию среды исполнения • Много шумового кода Было • Parallel.Invoke(() => DoGetData(startDate, endDate, requestId, config, jumperings, receiverUri)); • Не потребляет дополнительных ресурсов, используя общий планировщик задач • Стартует без дополнительных пауз, если очередь задач пуста • Учитывает количество логических процессоров в системе • Лаконичный синтаксис Стало
  • 5.
    Синхронизация • var endRead= new AutoResetEvent(false); • Надо явно создавать системные примитивы синхронизации • new Thread(() => { action(endRead); }.Start(); • Явное создание потока с передачей примитива • if(!WaitHandle.WaitAll(new[] {endRead, EndWrite}, ExecutionTimeout)) • Ожидание события • Снова много инфраструктурного кода, расход ресурсов, задержки Было • var readTask = Task.TaskFactory.StartNew(() => { action(); }); • Создание и запуск логической задачи – потоками занимается планировщик • if (!Task.WaitAll(new[] {readTask, sendTask}, ExecutionTimeout)) • Ожидание окончания выполнения задачи • Код более лаконичный, прозрачный, декларативный, системные ресурсы сам по себе не потребляет Стало
  • 6.
    Передача данных междузадачами •var pipe = new BlockingCollection<TrendValue>(PortionSize); •Размер порции влияет только на количество блокировок и переключений задач (чем больше порция – тем меньше) •И для источника и для приемника канал бесконечен, хотя потребляет конечный (ограниченный размером порции) объем памяти Создание канала •pipe.Add(value); •И так можно делать сколь угодно много раз •pipe.CompleteAdding(); •Если данных больше нет и не будет – это надо указать явно Запись в канал задачей- источником •foreach (var value in buffer.GetConsumingEnumerable()) •Для приемника весь поток данных предстает в виде одной большой последовательности •Закончится последовательность только если (и когда) источник подтвердит отсутствие данных Чтение из канала задачей-приемником •С использованием специальных структур данных получается очень лаконичный , прозрачный , нетребовательный к ресурсам код, позволяющий параллельно выполнять длительные конвеерные операции Резюме
  • 7.
    Обработка исключений • Необработанноеисключение убьет процесс Исключения в задачах не теряются • Task.WaitAll(readTask, sendTask); • Если хотя бы одна из задач завершилась с исключением, то оно будет выброшено в этой строке Исключения пробрасываются в место ожидания задачи • Если планируется перехват исключений более чем от одной задачи, то обязательно надо ловить AggregateException Исключения от нескольких задач группируются в AggregateException
  • 8.
    Отмена выполнения • varcts = CancellationTokenSource(); Создание источника отмены • var token = cts.Token; • Task.TaskFactory.StartNew(() => action(token), token); Передача токена задаче • cts.Cancel();Отмена задачи • token.ThrowIfCancellationRequested(); Проверка отмены в задаче • Исключение OperationCancelledException Результат отмененной задачи
  • 9.
    Параллелизм данных •Было: foreach(var item in SourceCollection) process(item); •Стало: Parallel.ForEach(sourceCollection, item => { process(item); }); •Отмена: Parallel.ForEach(enumerable, new ParallelOptions(){CancellationToken = cts.Token}, item => {process(item);}); foreach •Было: for(var i = 1; i < 10; i++) process(i); •Стало: Parallel.For(0, 10, i => {process(i);});for
  • 10.
    PLINQ •foreach(var item infrom i in enumerable.AsParallel() select compute(i))Просто •foreach(var item in from i in enumerable.AsParallel().AsOrdered() select compute(i))С сохранением порядка •foreach(var item in from i in enumerable.AsParallel.WithCancellation(cts.Token) select compute(i))С возможностью отмены •(from i in enumerable.AsParallel() select compute(i)).ForAll(item => {action(item);}) С параллельной обработкой результатов •Надо ловить AggregateExceptionОбработка исключений
  • 11.
    Потокобезопасные коллекции • vardictionary = new Dictionary<TKey, TValue>(); • lock(_locker){dictionary[key] = value;} Было • var dictionary = new ConcurrentDictionary<TKey, TValue> (); • Dictionary[key] = value; Стало