Томский политехнический университет
Опубликован: 23.01.2013 | Доступ: свободный | Студентов: 1158 / 192 | Длительность: 12:09:00
Лекция 8:

Parallel LINQ (PLINQ)

Введение в PLINQ

PLINQ (Параллельный LINQ) - это распараллеленная реализация LINQ. Запросы PLINQ схожи с не параллельными запросами LINQ, основное отличие этих запросов заключается в том, что PLINQ полностью использует возможности всех процессоров в системе. Результаты запросов PLINQ, также как и последовательные запросы LINQ, возвращают тип IEnumerable<T>, данный результат достигается путем разделения источника данных на сегменты и параллельной обработки запроса каждого сегмента в отдельном рабочем потоке на нескольких процессорах.

Благодаря PLINQ в значительной степени увеличивается производительность по сравнению с устаревшим кодом. Однако параллелизм может привести к появлению собственных сложностей, и не все операции запросов выполняются быстрее в PLINQ.

Все основные методы PLINQ содержаться в классе System.Linq.ParallelEnumerable - пространства имен System.Linq компилируются в сборку System.Core.dll. Данный класс включает в себя, реализации всех стандартных операторов запросов, поддерживаемых LINQ to Objects.

Кроме стандартных операторов-запросов класс ParallelEnumerable содержит набор методов, которые обеспечивают поведения, характерные для параллельного выполнения. Эти методы представлены в Табл. 11.2.

Таблица 11.2. Основные методы класса ParallelEnumerable
Методы класса ParallelEnumerable Описание
AsParallel Точка входа для PLINQ. Указывает на необходимость параллелизации остальной части запроса, если это возможно.
AsSequential<TSource> Указывает на необходимость последовательного выполнения остальной части запроса как непараллельного запроса LINQ.
AsOrdered Указывает, что PLINQ должен сохранить порядок исходной последовательности для остальной части запроса или до тех пор, пока порядок не будет изменен.
AsUnordered<TSource> Указывает, что PLINQ не должен сохранять порядок исходной последовательности для остальной части запроса.
WithCancellation<TSource> Указывает, что PLINQ должен периодически отслеживать состояние предоставленного токена отмены и отменять выполнение при запросе.
WithDegreeOfParallelism<TSource> Указывает максимальное количество процессоров, которое должен использовать PLINQ для параллелизации запроса.
WithMergeOptions<TSource> Предоставляет подсказку о том, как PLINQ должен, если это возможно, выполнять слияние параллельных результатов в одну последовательность в потоке-потребителе.
WithExecutionMode<TSource> Указывает, должен ли PLINQ выполнять параллелизацию запроса, даже если согласно поведению по умолчанию он будет выполняться последовательно.
ForAll<TSource> Многопоточный метод перечисления, который в отличие от итерации результатов запроса позволяет обрабатывать результаты параллельно без предварительного слияния в поток-потребитель.
Перегрузка Aggregate Уникальная для PLINQ перегрузка, обеспечивающая промежуточное агрегирование локальных частей потока, и предоставляющая функцию окончательного агрегирования для объединения результатов всех частей.

Метод AsParallel

Метод AsParallel() - это некая входная точка в использовании запросов PLINQ. Он преобразует последовательность данных в ParallelQuery. Механизм LINQ обнаруживает использование ParallelQuery в качестве источника в запросе и переключается на выполнение PLINQ автоматически. Метод AsParallel() несколько типов перегрузки, которые представлены ниже:

  • Первый тип работает с использованием IEnumerable <TSource> и возвращает экземпляр ParallelQuery <TSource>, который может быть использован в качестве основы запроса PLINQ:
    public static ParallelQuery<TSource> AsParallel<TSource>(
    this IEnumerable<TSource> source 
    )
  • Второй тип создает экземпляр ParallelQuery из IEnumerable и предназначен для поддержки унаследованных коллекций, таких как System.Collections.ArrayList. Объект ParallelQuery не является строго типизированным и не может использоваться в качестве основы запроса PLINQ без преобразования в ParallelQuery:
    public static ParallelQuery AsParallel(
    this IEnumerable source
    )

Оба типа возвращают исключение вида ArgumentNullException.

Пример использования PLINQ запроса

Что бы преобразовать LINQ в PLINQ, в запросе LINQ достаточно вызвать метод AsParallel() в LINQ запросе. В следующем примере находятся числа в диапазоне от 1 до 100000, кратные двум с помощью неоптимизированного параллельного алгоритма:

var source = Enumerable.Range(1, 100000);
            var parallelQuery = from num in source.AsParallel()
                                where num % 2 == 0
                                select num;
  foreach (var n in parallelQuery)
            {
                Console.WriteLine(n);
            }
            Console.ReadLine();

Этот же запрос можно было записать с использованием лямда-выражения:

var parallelQuery = source.AsParallel().Where(n => n % 2 == 0).Select(n => n);
 Результаты выполнения запроса PLINQ

увеличить изображение
Рис. 11.5. Результаты выполнения запроса PLINQ

Существует ряд причин, по которым, PLINQ не используется по умолчанию. Первое, использование PLINQ целесообразно использовать при наличии значительного количества вычислительных задач, распределенных по рабочим потокам. Многие запросы LINQ выполняются очень быстро, и распараллеливание не только не нужно, но и нецелесообразно, так как накладные расходы на разделение данных, объединения результатов и координацию дополнительных потоков могут, на самом деле, только ухудшить производительность. В следующем списке описаны формы запросов, которые PLINQ по умолчанию будет выполнять в последовательном режиме:

  • Запросы, которые содержат индексированный Where, индексированный SelectMany или ElementAt после оператора упорядочивания или фильтрации, который удаляет или переупорядочивает исходные индексы.
  • Запросы, которые содержат оператор Take, TakeWhile, Skip, SkipWhile и в которых индексы в исходной последовательности не находятся в исходном порядке.
  • Запросы, которые содержат Zip или SequenceEquals за исключением, когда один из источников данных содержит изначально упорядоченный индекс, а другие источники данных можно проиндексировать (то есть массив или IList(T)).
  • Запросы, которые содержат Concat, за исключением случая, если они применяются к индексируемым источникам данных.
  • Запросы, которые содержат Reverse, за исключением случая, если они применяются к индексируемым источникам данных.
Владимир Каширин
Владимир Каширин

Вопрос по Курсу: "Параллельное программирование с использованием MS VisualStudia 2010".

При компиляции Самостоятельного задания (одновременная отрисовка прямоугольников, эллипсов и выдача в текст-бокс случайного числа) среда предупреждает: suspend - устаревшая команда; примените monitor, mutex и т.п.

Создаётся впечатление, что Задание создано в более поздней среде, чем VS 2010.