В эпоху многоядерных машин, которые позволяют параллельно выполнять сразу несколько процессов, стандартных средств работы с потоками в .NET уже оказалось недостаточно. Поэтому во фреймворк .NET была добавлена библиотека параллельных задач TPL (Task Parallel Library), основной функционал которой располагается в пространстве имен System.Threading.Tasks . Данная библиотека упрощает работу с многопроцессорными, многоядерными система. Кроме того, она упрощает работу по созданию новых потоков. Поэтому обычно рекомендуется использовать именно TPL и ее классы для создания многопоточных приложений, хотя стандартные средства и класс Thread по-прежнему находят широкое применение.
В основе библиотеки TPL лежит концепция задач, каждая из которых описывает отдельную продолжительную операцию. В библиотеке классов .NET задача представлена специальным классом — классом Task , который находится в пространстве имен System.Threading.Tasks . Данный класс описывает отдельную задачу, которая запускается асинхронно в одном из потоков из пула потоков. Хотя ее также можно запускать синхронно в текущем потоке.
Не удалось запустить приложение, поскольку его параллельная конфигурация неправильна
Для определения и запуска задачи можно использовать различные способы.
-
Первый способ создание объекта Task и вызов у него метода Start:
Task task = new Task(() => Console.WriteLine(«Hello Task!»)); task.Start();
Task task = Task.Factory.StartNew(() => Console.WriteLine(«Hello Task!»));
Task task = Task.Run(() => Console.WriteLine(«Hello Task!»));
Определим небольшую программу, где используем все эти способы:
Task task1 = new Task(() => Console.WriteLine(«Task1 is executed»)); task1.Start(); Task task2 = Task.Factory.StartNew(() => Console.WriteLine(«Task2 is executed»)); Task task3 = Task.Run(() => Console.WriteLine(«Task3 is executed»));
Итак, в данном коде задачи создаются и запускаются, но при выполнении приложения на консоли мы можем не увидеть ничего. Почему? Потому что когда поток задачи запускается из основного потока программы — потока метода Main, приложение может завершить выполнение до того, как все три или даже хотя бы одна из трех задач начнет выполнение. Чтобы этого не произошло, мы можем программным образом ожидать завершения задачи.
Ожидание завершения задачи
Чтобы приложение ожидало завершения задачи, можно использовать метод Wait() объекта Task:
Task task1 = new Task(() => Console.WriteLine(«Task1 is executed»)); task1.Start(); Task task2 = Task.Factory.StartNew(() => Console.WriteLine(«Task2 is executed»)); Task task3 = Task.Run(() => Console.WriteLine(«Task3 is executed»)); task1.Wait(); // ожидаем завершения задачи task1 task2.Wait(); // ожидаем завершения задачи task2 task3.Wait(); // ожидаем завершения задачи task3
Возможный консольный вывод программы:
Task3 is executed Task2 is executed Task1 is executed
Консольный вывод не детерминирован, поскольку задачи не выполняются последовательно. Первая запущенная задача может завершить свое выполнение после последней задачи.
Как запустить приложение, если его параллельная конфигурация неправильна
Стоит отметить, что метод Wait() блокирует вызывающий поток, в котором запущена задача, пока эта задача не завершит свое выполнение. Например:
Console.WriteLine(«Main Starts»); // создаем задачу Task task1 = new Task(() => < Console.WriteLine(«Task Starts»); Thread.Sleep(1000); // задержка на 1 секунду — имитация долгой работы Console.WriteLine(«Task Ends»); >); task1.Start(); // запускаем задачу task1.Wait(); // ожидаем выполнения задачи Console.WriteLine(«Main Ends»);
Для эмуляции долговременной работы здесь в задаче task1 устанавливается задержка на 1 секунду. В итоге, когда выполнение дойдет до вызова task1.Wait() основной поток остановит свое выполнение и будет ждать завершения задачи. И мы получим следующий консольный вывод:
Main Starts Task Starts Task Ends Main Ends
Если подобное поведение не принципиально, то ожидание завершения задачи можно поместить в конец метода Main:
Console.WriteLine(«Main Starts»); // создаем задачу Task task1 = new Task(() => < Console.WriteLine(«Task Starts»); Thread.Sleep(1000); // задержка на 1 секунду — имитация долгой работы Console.WriteLine(«Task Ends»); >); task1.Start(); // запускаем задачу Console.WriteLine(«Main Ends»); task1.Wait(); // ожидаем выполнения задачи
В этом случае приложение все равно будет ждать завершения задачи, однако другие синхронные действия в основном потоке не будут блокироваться и ожидать завершения задачи.
Синхронный запуск задачи
По умолчанию задачи запускаются асинхронно. Однако с помощью метода RunSynchronously() можно запускать синхронно:
Console.WriteLine(«Main Starts»); // создаем задачу Task task1 = new Task(() => < Console.WriteLine(«Task Starts»); Thread.Sleep(1000); Console.WriteLine(«Task Ends»); >); task1.RunSynchronously(); // запускаем задачу синхронно Console.WriteLine(«Main Ends»); // этот вызов ждет завершения задачи task1
Свойства класса Task
Класс Task имеет ряд свойств, с помощью которых мы можем получить информацию об объекте. Некоторые из них:
- AsyncState : возвращает объект состояния задачи
- CurrentId : возвращает идентификатор текущей задачи (статическое свойство)
- Id : возвращает идентификатор текущей задачи
- Exception : возвращает объект исключения, возникшего при выполнении задачи
- Status : возвращает статус задачи. Представляет перечисление System.Threading.Tasks.TaskStatus , которое имеет следующие значения:
- Canceled : задача отменена
- Created : задача создана, но еще не запущена
- Faulted : в процессе работы задачи произошло исключение
- RanToCompletion : задача успешно завершена
- Running : задача запущена, но еще не завершена
- WaitingForActivation : задача ожидает активации и постановки в график выполнения
- WaitingForChildrenToComplete : задача завершена и теперь ожидает завершения прикрепленных к ней дочерних задач
- WaitingToRun : задача поставлена в график выполнения, но еще не начала свое выполнение
Используем некоторые из этих свойств:
Task task1 = new Task(() => < Console.WriteLine($»TaskStarts»); Thread.Sleep(1000); Console.WriteLine($»Task Ends»); >); task1.Start(); //запускаем задачу // получаем информацию о задаче Console.WriteLine($»task1 Id: «); Console.WriteLine($»task1 is Completed: «); Console.WriteLine($»task1 Status: «); task1.Wait(); // ожидаем завершения задачи
Пример консольного вывода:
task1 Id: 1 Task1 Starts task1 is Completed: False task1 Status: Running Task1 Ends
Источник: metanit.com
PARALLEL.RU — Информационно-аналитический центр по параллельным вычислениям
Для начинающих пользователей вычислительных кластеров
Данная страница написана с таким расчетом, чтобы она могла быть полезной не только пользователям вычислительных кластеров НИВЦ, но и всем, желающим получить представление о работе вычислительного кластера. Решение типичных проблем пользователей кластера НИВЦ изложено на отдельной странице.
Что такое вычислительный кластер?
В общем случае, вычислительный кластер — это набор компьютеров (вычислительных узлов), объединенных некоторой коммуникационной сетью. Каждый вычислительный узел имеет свою оперативную память и работает под управлением своей операционной системы. Наиболее распространенным является использование однородных кластеров, то есть таких, где все узлы абсолютно одинаковы по своей архитектуре и производительности.
Подробнее о том, как устроен и работает вычислительный кластер можно почитать в книге А.Лациса «Как построить и использовать суперкомпьютер».
Как запускаются программы на кластере?
Для каждого кластера имеется выделенный компьютер — головная машина (front-end). На этой машине установлено программное обеспечение, которое управляет запуском программ на кластере. Собственно вычислительные процессы пользователей запускаются на вычислительных узлах, причем они распределяются так, что на каждый процессор приходится не более одного вычислительного процесса. Запускать вычислительные процессы на головной машине кластера нельзя.
Пользователи имеют терминальный доступ на головную машину кластера, а входить на узлы кластера для них нет необходимости. Запуск программ на кластере осуществляется в т.н. «пакетном» режиме — это значит, что пользователь не имеет непосредственного, «интерактивного» взаимодействия с программой, программа не может ожидать ввода данных с клавиатуры и выводить непосредственно на экран. Более того, программа пользователя может работать тогда, когда пользователь не подключен к кластеру.
Какая установлена операционная система?
Вычислительный кластер, как правило, работает под управлением одной из разновидностей ОС Unix — многопользовательской многозадачной сетевой операционной системы. В частности, в НИВЦ МГУ кластеры работают под управлением ОС Linux — свободно распространяемого варианта Unix. Unix имеет ряд отличий от Windows, которая обычно работает на персональных компьютерах, в частности эти отличие касаются интерфейса с пользователем, работы с процессами и файловой системы.
Более подробно об особенностях и командах ОС UNIX можно почитать здесь:
- Инсталляция Linux и первые шаги (книга Matt Welsh, перевод на русский язык А.Соловьева).
- Учебник по Unix для начинающих.
- Энциклопедия Linux.
- Операционная система UNIX (информационно-аналитические материалы на сервере CIT-Forum).
Как хранятся данные пользователей?
Все узлы кластера имеют доступ к общей файловой системе, находящейся на файл-сервере. То есть файл может быть создан, напрмер, на головной машине или на каком-то узле, а затем прочитан под тем же именем на другом узле. Запись в один файл одновременно с разных узлов невозможна, но запись в разные файлы возможна.
Кроме общей файловой системы, могут быть локальные диски на узлах кластера. Они могут использоваться программами для хранения временных файлов. После окончания (точнее, непосредственно перед завершением) работы программы эти файлы должны удаляться.
Какие используются компиляторы?
Никаких специализированных параллельных компиляторов для кластеров не существует. Используются обычные оптимизирующие компиляторы с языков Си и Фортран — GNU, Intel или другие, умеющие создавать исполняемые программы ОС Linux. Как правило, для компиляции параллельных MPI-программ используются специальные скрипты (mpicc, mpif77, mpif90 и др.), которые являются надстройками над имеющимися компиляторами и позволяют подключать необходимые библиотеки.
Как использовать возможности кластера?
Существует несколько способов задействовать вычислительные мощности кластера.
1. Запускать множество однопроцессорных задач. Это может быть разумным вариантом, если нужно провести множество независимых вычислительных экспериментов с разными входными данными, причем срок проведения каждого отдельного расчета не имеет значения, а все данные размещаются в объеме памяти, доступном одному процессу.
2. Запускать готовые параллельные программы. Для некоторых задач доступны бесплатные или коммерческие параллельные программы, которые при необходимости Вы можете использовать на кластере. Как правило, для этого достаточно, чтобы программа была доступна в исходных текстах, реализована с использованием интерфейса MPI на языках С/C++ или Фортран. Примеры свободно распространяемых параллельных программ, реализованных с помощью MPI: GAMESS-US (квантовая химия), POVRay-MPI (трассировка лучей).
3. Вызывать в своих программах параллельные библиотеки. Также для некоторых областей, таких как линейная алгебра, доступны библиотеки, которые позволяют решать широкий круг стандартных подзадач с использованием возможностей параллельной обработки.
Если обращение к таким подзадачам составляет большую часть вычислительных операций программы, то использование такой параллельной библиотеки позволит получить параллельную программу практически без написания собственного параллельного кода. Примером такой библиотеки является SCALAPACK. Русскоязычное руководство по использованию этой библиотеки и примеры можно найти на сервере по численному анализу НИВЦ МГУ. Также доступна параллельная библиотека FFTW для вычисления быстрых преобразований Фурье (БПФ). Информацию о других параллельных библиотеках и программах, реализованных с помощью MPI, можно найти по адресу http://www-unix.mcs.anl.gov/mpi/libraries.html.
4. Создавать собственные параллельные программы. Это наиболее трудоемкий, но и наиболее универсальный способ. Существует два основных варианта. 1) Вставлять параллельные конструкции в имеющиеся параллельные программы. 2) Создавать «с нуля» параллельную программу.
Как работают параллельные программы на кластере?
Параллельные программы на вычислительном кластере работают в модели передачи сообщений (message passing). Это значит, что программа состоит из множества процессов, каждый из которых работает на своем процессоре и имеет свое адресное пространство.
Причем непосредственный доступ к памяти другого процесса невозможен, а обмен данными между процессами происходит с помощью операций приема и посылки сообщений. То есть процесс, который должен получить данные, вызывает операцию Receive (принять сообщение), и указывает, от какого именно процесса он должен получить данные, а процесс, который должен передать данные другому, вызывает операцию Send (послать сообщение) и указывает, какому именно процессу нужно передать эти данные. Эта модель реализована с помощью стандартного интерфейса MPI. Существует несколько реализаций MPI, в том числе бесплатные и коммерческие, переносимые и ориентированные на конкретную коммуникационную сеть.
Как правило, MPI-программы построены по модели SPMD (одна программа — много данных), то есть для всех процессов имеется только один код программы, а различные процессы хранят различные данные и выполняют свои действия в зависимости от порядкового номера процесса.
Более подробно об MPI можно почитать здесь:
- Курс Вл.В.Воеводина «Параллельная обработка данных». Лекция 5. Технологии параллельного программирования. Message Passing Interface.
- Вычислительный практикум по технологии MPI (А.С.Антонов).
- А.С.Антонов «Параллельное программирование с использованием технологии MPI».
- MPI: The Complete Reference (на англ.яз.).
- Глава 8: Message Passing Interface в книге Яна Фостера «Designing and Building Parallel Programs» (на англ.яз.).
Где можно посмотреть примеры параллельных программ?
Схематичные примеры MPI-программ можно посмотреть здесь:
- Курс Вл.В.Воеводина «Параллельная обработка данных». Приложение к лекции 5.
- Примеры из пособия А.С.Антонова «Параллельное программирование с использованием технологии MPI».
Примеры простейших работающих MPI-программ доступны в составе пакета MPICH, свободно распространяемой реализации MPI. Для пользователей НИВЦ МГУ простейшие примеры MPI-программ на Си и Фортране доступны в директории /home/examples/mpi. Примеры использования конструкций MPI в программах на языке Си можно посмотреть в тестах производительности для параллельных компьютеров. Примеры программ на Фортране с комментариями можно посмотреть в англоязычном документе «MPI User’s Guide in Fortran» (формат Word).
Можно ли отлаживать параллельные программы на персональном компьютере?
Разработка MPI-программ и проверка функциональности возможна на обычном ПК. Можно запускать несколько MPI-процессов на однопроцессорном компьютере и таким образом проверять работоспособность программы. Желательно, чтобы это был ПК с ОС Linux, где можно установить пакет MPICH. Это возможно и на компьютере с Windows, но более затруднительно.
Насколько трудоемко программировать вычислительные алгоритмы c помощью MPI и есть ли альтернативы?
Набор функций интерфейса MPI иногда называют «параллельным ассемблером», т.к. это система программирования относительно низкого уровня. Для начинающего пользователя-вычислителя может быть достаточно трудоемкой работой запрограммировать сложный параллельный алгоритм с помощью MPI и отладить MPI-программу. Существуют и более высокоуровневые системы программирования, в частности российские разработки — DVM и НОРМА, которые позволяют пользователю записать задачу в понятных для него терминах, а на выходе создают код с использованием MPI, и поэтому могут быть использованы практически на любом вычислительном кластере.
Как ускорить проведение вычислений на кластере?
Во-первых, нужно максимально ускорить вычисления на одном процессоре, для чего можно принять следующие меры.
1. Подбор опций оптимизации компилятора. Подробнее об опциях компиляторов можно почитать здесь:
- Компиляторы Intel C++ и Fortran (русскоязычная страница на нашем сайте).
- GCC online documentation.
2. Использование оптимизированных библиотек. Если некоторые стандартные действия, такие как умножение матриц, занимают значительную долю времени работы программы, то имеет смысл использовать готовые оптимизированные процедуры, выполняющие эти действия, а не программировать их самостоятельно.
Для выполнения операций линейной алгебры над матричными и векторными величинами была разработана библиотека BLAS («базовые процедуры линейной алгебры»). Интерфейс вызова этих процедур стал уже фактически стандартом и сейчас существуют несколько хорошо оптимизированных и адаптированных к процессорным архитектурам реализаций этой библиотеки. Одной из таких реализаций является свободно распространяемая библиотека ATLAS, которая при установке настраивается с учетом особенностей процессора. Компания Интел предлагает библиотеку MKL — оптимизированную реализацию BLAS для процессоров Intel и SMP-компьютеров на их основе. Тут статья про подбор опций MKL.
Подробнее о библиотеках линейной алгебры (BLAS) можно почитать здесь:
3. Исключение своппинга (автоматического сброса данных из памяти на диск). Каждый процесс должен хранить не больше данных, чем для него доступно оперативной памяти (в случае двухпроцессорного узла это примерно половина от физической памяти узла). В случае необходимости работать с большим объемом данных может быть целесообразным организовать работу со временными файлами или использовать несколько вычислительных узлов, которые в совокупности предоставляют необходимый объем оперативной памяти.
4. Более оптимальное использование кэш-памяти. В случае возможности изменять последовательность действий программы, нужно модифицировать программу так, чтобы действия над одними и те же или подряд расположенными данными данными выполнялись также подряд, а не «в разнобой». В некоторых случаях может быть целесообразно изменить порядок циклов во вложенных циклических конструкциях. В некоторых случаях возможно на «базовом» уровне организовать вычисления над такими блоками, которые полностью попадают в кэш-память.
5. Более оптимальная работа с временными файлами. Например, если программа создает временные файлы в текущем каталоге, то более разумно будет перейти на использование локальных дисков на узлах. Если на узле работают два процесса и каждый из них создает временные файлы, и при этом на узле доступны два локальных диска, то нужно, чтобы эти два процесса создавали файлы на разных дисках.
6. Использование наиболее подходящих типов данных. Например, в некоторых случаях вместо 64-разрядных чисел с плавающей точкой двойной точности (double) может быть целесообразным использовать 32-разрядные числа одинарной точности (float) или даже целые числа (int).
Более подробно о тонкой оптимизации программ можно почитать в руководстве по оптимизации для процессоров Intel и в других материалах по этой теме на веб-сайте Intel.
Как оценить и улучшить качество распараллеливания?
Для ускорения работы параллельных программ стоит принять меры для снижения накладных расходов на синхронизацию и обмены данными. Возможно, приемлемым подходом окажется совмещение асинхронных пересылок и вычислений. Для исключения простоя отдельных процессоров нужно наиболее равномерно распределить вычисления между процессами, причем в некоторых случаях может понадобиться динамическая балансировка.
Важным показателем, который говорит о том, эффективно ли в программе реализован параллелизм, является загрузка вычислительных узлов, на которых работает программа. Если загрузка на всех или на части узлов далека от 100% — значит, программа неэффективно использует вычислительные ресурсы, т.е. создает большие накладные расходы на обмены данными или неравномерно распределяет вычисления между процессами. Пользователи НИВЦ МГУ могут посмотреть загрузку через веб-интерфейс для просмотра состояния узлов.
В некоторых случаях для того, чтобы понять, в чем причина низкой производительности программы и какие именно места в программе необходимо модифицировать, чтобы добиться увеличения производительности, имеет смысл использовать специальные средства анализа производительности — профилировщики и трассировщики. На кластере Ant установлена система для отладки MPI-программ deb-MPI. Краткую информацию по системе можно найти по адресу http://parallel.ru/cluster/deb-MPI-UG.html .—>
Подробнее об улучшении производительности параллельных программ можно почитать в книге В.В.Воеводина и Вл.В.Воеводина «Параллельные вычисления».
Источник: parallel.ru
MNorin.com
Блог про Linux, Bash и другие информационные технологии
Параллельное выполнение в bash
В большинстве командных оболочек команды выполняются по умолчанию последовательно. И это, в принципе, нормально. Потому что человек с системой взаимодействует последовательно, обычно нет необходимости несколько команд выполнять параллельно. Bash в этом смысле тоже не исключение. Но при автоматизации возможность параллельного выполнения может быть полезной.
Давайте посмотрим, как организовать параллельное выполнение в bash.
Использование фонового режима
Для организации параллельной работы нескольких программ часто используется запуск в фоновом режиме при помощи знака амперсанда —
Команда будет работать в фоне, при этом из текущей оболочки можно выполнять команды. Таким образом уже можно распараллелить какие-то действия. Можно запустить сразу несколько команд таким образом и дождаться, пока они все отработают. Для ожидания запущенных дочерних процессов используется команда wait.
Эта команда без параметров ожидает окончания работы всех дочерних процессов, соответственно, для ожидания окончания 5 процессов понадобится выполнить команду всего 1 раз. В принципе, это легко реализуется через цикл. Например, так:
for i in do # запуск одного фонового процесса sleep 10 echo $i dev/null | command2 param1 param2 > /dev/null | command3
Этот вариант до использования в скриптах, естественно, нужно обязательно проверить в ручном режиме и посмотреть в страницах руководств используемых программ (если там есть такая информация), что имеет больший приоритет — опции командной строки или стандартный поток ввода, потому что некоторые программы могут игнорировать командную строку, если данные передаются через стандартный поток ввода.
Параллельное выполнение и ограничение количества фоновых задач в скрипте
Давайте рассмотрим такую практическую задачу — запустить 100 процессов параллельно, но так, чтобы работало одновременно не более 10 процессов. В общем, достаточно простая задача. Предположим, что все процессы работают произвольное количество времени. Пусть запуск одной задачи будет выглядеть как запуск команды sleep со случайным параметром от 0 до 29. Тогда скрипт будет выглядеть следующим образом:
#!/bin/bash RANDOM=10 JOBS_COUNTER=0 MAX_CHILDREN=10 MY_PID=$$ for i in do echo Cycle counter: $i JOBS_COUNTER=$((`ps ax -Ao ppid | grep $MY_PID | wc -l`)) while [ $JOBS_COUNTER -ge $MAX_CHILDREN ] do JOBS_COUNTER=$((`ps ax -Ao ppid | grep $MY_PID | wc -l`)) echo Jobs counter: $JOBS_COUNTER sleep 1 done sleep $(($RANDOM % 30)) https://mnorin.com/parallelnoe-vypolnenie-v-bash.html» target=»_blank»]mnorin.com[/mask_link]