Чтобы продемонстрировать насколько элегантно С# позволяет организовать параллельные вычисления, реализуем классическую задачу поиска простых чисел в заданном диапазоне множества натуральных чисел.
Для начала, я создам функцию-расширение для класса целых чисел int, определяющую является ли число простым.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
static class IntUtilities { public static bool isSimple(this int Number) { double j = 2; bool Simple = true; while (j <= Number / 2) { if (Math.Floor(Number / j) == Number / j) { Simple = false; break; } j++; } return Simple; } } |
Функция isSimple обернута в статический класс и реализует нужный нам метод проверки, является ли число простым.
Следующим шагом создадим класс-источник целых чисел. Его особенностью является использование конструкции yield при создании списка, что позволит нам не развертывать весь массив тестируемых чисел одномоментно в памяти, а выдавать их по требованию программы.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class NumberSource { private int _maxValue; public NumberSource(int MaxValue) { _maxValue = MaxValue; } public IEnumerable<int> getList() { int i = 2; while (i < _maxValue) { yield return i++; } } } |
Для инициализации массива мы передаём верхнюю планку — MaxValue. Т.е. массив состоит из чисел [2 … MaxValue — 1].
На данном этапе мы подготовили массив и функцию, которая выполняет независимые вычисления с отдельным элементом этого массива. Т.е. есть все необходимые условия, чтобы по возможности использовать параллельные вычисления.
Одно-поточный вариант
Далее посмотрим как выглядит код, выполняющий вычисления в одном потоке, чтобы впоследствии сравнить с много-поточной версией кода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Program { static void Main(string[] args) { int top = 150000; var NS = new NumberSource(top); // получаем список NS.getList() // используем расширение LINQ // для фильтрации списка .Where(num => num.isSimple()) // и его сортировки, .OrderBy(num => num) // преобразуем IEnumerable в List .ToList() // и выводим результат вычислений .ForEach(num => Console.WriteLine(num.ToString() + " is simple")); } } |
Для одно-поточной версии сортировка является лишней, т.к. числа поставляются итак в нужном порядке. Сортировка пригодится в многопоточной версии.
Параллельные вычисления
Сравните код показанный ранее со следующим:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Program { static void Main(string[] args) { int top = 150000; var NS = new NumberSource(top); // получаем список NS.getList() // распараллеливаем дальнейшие операции .AsParallel() .Where(num => num.isSimple()) .OrderBy(num => num) .ToList() .ForEach(num => Console.WriteLine(num.ToString() + " is simple")); } } |
Мощное расширение AsParallel() в пространстве имен System.Linq прячет от нас все технические особенности того, как наш код превращается в многопоточный.