Database
 sql >> база данни >  >> RDS >> Database

Основи на паралелното програмиране с рамката Fork/Join в Java

С навлизането на многоядрените процесори през последните години, паралелното програмиране е начинът да се възползвате напълно от новите работни коне за обработка. Паралелно програмиране се отнася до едновременното изпълнение на процеси поради наличието на множество ядра за обработка. Това по същество води до огромен тласък на производителността и ефективността на програмите, за разлика от линейното едноядрено изпълнение или дори многонишковия. Рамката за Fork/Join е част от API за паралелност на Java. Тази рамка позволява на програмистите да паралелизират алгоритми. Тази статия изследва концепцията за паралелно програмиране с помощта на Fork/Join Framework, налична в Java.

Общ преглед

Паралелното програмиране има много по-широка конотация и несъмнено е обширна област, която трябва да се разработи в няколко реда. Същността на въпроса е доста проста, но оперативно много по-трудна за постигане. С прости думи, паралелното програмиране означава писане на програми, които използват повече от един процесор за изпълнение на задача, това е всичко! Познай какво; звучи познато, нали? Почти се римува с идеята за многонишковост. Но имайте предвид, че има някои важни разлики между тях. На повърхността те са еднакви, но подводното течение е абсолютно различно. Всъщност многонишковостта беше въведена, за да осигури нещо като илюзия за паралелна обработка без никакво реално паралелно изпълнение. Това, което многонишковостта наистина прави, е, че краде времето за престой на процесора и го използва в своя полза.

Накратко, многонишковостта е колекция от дискретни логически единици от задачи, които се изпълняват, за да грабнат своя дял от времето на процесора, докато друга нишка може временно да чака, да речем, някакъв потребителски вход. Времето на неактивен процесор се споделя оптимално между конкуриращите се нишки. Ако има само един процесор, времето е споделено. Ако има няколко ядра на процесора, те също са споделени през цялото време. Следователно, оптималната многонишкова програма изстисква производителността на процесора чрез интелигентния механизъм за споделяне на времето. По същество това винаги е една нишка, използваща един процесор, докато друга нишка чака. Това се случва по фин начин, така че потребителят получава усещане за паралелна обработка, където в действителност обработката всъщност се случва в бърза последователност. Най-голямото предимство на многонишковостта е, че това е техника за извличане на максимална полза от ресурсите за обработка. Сега тази идея е доста полезна и може да се използва във всеки набор от среди, независимо дали има един или няколко процесора. Идеята е същата.

Паралелното програмиране, от друга страна, означава, че има множество специализирани процесори, които се впрягат паралелно от програмиста. Този тип програмиране е оптимизиран за многоядрена CPU среда. Повечето от съвременните машини използват многоядрени процесори. Следователно паралелното програмиране е доста актуално в днешно време. Дори и най-евтината машина е монтирана с многоядрени процесори. Вижте ръчните устройства; дори те са многоядрени. Въпреки че всичко изглежда банално с многоядрени процесори, ето и друга страна на историята. Повече ядра на процесора означават ли по-бързи или ефективни изчисления? Не винаги! Алчната философия на „колкото повече, толкова по-добре“ не е приложима нито за компютрите, нито за живота. Но те са там, без да се пренебрегват — двойни, четворни, окта и т.н. Те са там най-вече защото ги искаме, а не защото имаме нужда от тях, поне в повечето случаи. В действителност е сравнително трудно да поддържате зает дори един процесор в ежедневните изчисления. Въпреки това, многоядрените ядра имат своето приложение при специални обстоятелства, като например в сървъри, игри и т.н., или за решаване на големи проблеми. Проблемът с наличието на множество процесори е, че изисква памет, която трябва да съответства на скоростта с процесорната мощност, заедно със светкавично бързи канали за данни и други аксесоари. Накратко, множеството процесорни ядра в ежедневните изчисления осигуряват подобрение на производителността, което не може да надхвърли количеството ресурси, необходими за използването му. Следователно получаваме недостатъчно използвана скъпа машина, която може би е предназначена само за демонстрация.

Паралелно програмиране

За разлика от многонишковия, където всяка задача е дискретна логическа единица от по-голяма задача, задачите за паралелно програмиране са независими и техният ред на изпълнение няма значение. Задачите се дефинират според функцията, която изпълняват, или данните, използвани при обработката; това се нарича функционален паралелизъм или паралелизъм на данните , съответно. При функционален паралелизъм всеки процесор работи върху своя участък от проблема, докато при паралелизъм на данни процесорът работи върху своята част от данните. Паралелното програмиране е подходящо за по-голяма проблемна база, която не се вписва в една архитектура на процесора, или проблемът може да е толкова голям, че не може да бъде решен за разумна оценка от време. В резултат на това задачите, когато са разпределени между процесори, могат да получат резултата сравнително бързо.

Рамката на Fork/Join

Fork/Join Framework е дефинирана в java.util.concurrent пакет. Той включва няколко класа и интерфейси, които поддържат паралелно програмиране. Това, което прави преди всичко, е, че опростява процеса на създаване на множество нишки, тяхното използване и автоматизира механизма на разпределяне на процеси между множество процесори. Забележимата разлика между многонишковото и паралелното програмиране с тази рамка е много подобна на това, което споменахме по-рано. Тук обработващата част е оптимизирана за използване на множество процесори, за разлика от многонишковия, където времето на престой на единичния процесор се оптимизира на базата на споделено време. Допълнителното предимство на тази рамка е използването на многонишков процес в среда за паралелно изпълнение. Няма вреда.

Има четири основни класа в тази рамка:

  • ForkJoinTask: Това е абстрактен клас, който дефинира задача. Обикновено задачата се създава с помощта на fork() метод, дефиниран в този клас. Тази задача е почти подобна на обикновена нишка, създадена с Нишката клас, но е по-лек от него. Механизмът, който прилага, е, че позволява управление на голям брой задачи с помощта на малък брой действителни нишки, които се присъединяват към ForkJoinPool . fork() метод позволява асинхронно изпълнение на извикващата задача. join() методът позволява изчакване, докато задачата, за която е извикан, бъде окончателно прекратена. Има и друг метод, наречен invoke() , който съчетава вилицата и присъединете се операции в едно повикване.
  • ForkJoinPool: Този клас предоставя общ пул за управление на изпълнението на ForkJoinTask задачи. По същество той предоставя входна точка за подаване от не-ForkJoinTask клиенти, както и операции за управление и наблюдение.
  • Рекурсивно действие: Това също е абстрактно разширение на ForkJoinTask клас. Обикновено ние разширяваме този клас, за да създадем задача, която не връща резултат или има недействителност тип връщане. compute() методът, дефиниран в този клас, е отменен, за да включва изчислителен код на задачата.
  • Рекурсивна задача: Това е друго абстрактно разширение на ForkJoinTask клас. Разширяваме този клас, за да създадем задача, която връща резултат. И подобно на ResursiveAction, той също така включва защитен абстрактен compute() метод. Този метод е отменен, за да включи изчислителната част на задачата.

Стратегията за разклоняване/присъединяване

Тази рамка използва рекурсивно разделяй и владей стратегия за прилагане на паралелна обработка. Той основно разделя задача на по-малки подзадачи; след това всяка подзадача се разделя допълнително на подзадачи. Този процес се прилага рекурсивно за всяка задача, докато стане достатъчно малка, за да се обработва последователно. Да предположим, че трябва да увеличим стойностите на масив от N числа. Това е задачата. Сега можем да разделим масива на две, създавайки две подзадачи. Разделете всяка от тях отново на още две подзадачи и т.н. По този начин можем да приложим разделяй и владей стратегия рекурсивно, докато задачите се отделят в единичен проблем. След това този проблем на модула може да се изпълнява паралелно от наличните многоядрени процесори. В непаралелна среда това, което трябваше да направим, е да преминем през целия масив и да извършим обработката последователно. Това очевидно е неефективен подход с оглед на паралелната обработка. Но истинският въпрос е може ли всеки проблем да се раздели и победи ? Определено НЕ! Но има проблеми, които често включват някакъв вид масив, събиране, групиране на данни, които са особено подходящи за този подход. Между другото, има проблеми, които може да не използват събиране на данни, но все пак могат да бъдат оптимизирани за използване на стратегията за паралелно програмиране. Какъв тип изчислителни проблеми са подходящи за паралелна обработка или обсъждане на паралелен алгоритъм е извън обхвата на тази статия. Нека видим бърз пример за прилагането на Fork/Join Framework.

Бърз пример

Това е много прост пример, който да ви даде представа как да приложите паралелизъм в Java с рамката Fork/Join.

package org.mano.example;
import java.util.concurrent.RecursiveAction;
public class CustomRecursiveAction extends
      RecursiveAction {
   final int THRESHOLD = 2;
   double [] numbers;
   int indexStart, indexLast;
   CustomRecursiveAction(double [] n, int s, int l) {
      numbers = n;
      indexStart = s;
      indexLast = l;
   }
   @Override
   protected void compute() {
      if ((indexLast - indexStart) > THRESHOLD)
         for (int i = indexStart; i < indexLast; i++)
            numbers [i] = numbers [i] + Math.random();
         else
            invokeAll (new CustomRecursiveAction(numbers,
               indexStart, (indexStart - indexLast) / 2),
               new CustomRecursiveAction(numbers,
                  (indexStart - indexLast) / 2,
                     indexLast));
   }
}

package org.mano.example;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
public class Main {
   public static void main(String[] args) {
      final int SIZE = 10;
      ForkJoinPool pool = new ForkJoinPool();
      double na[] = new double [SIZE];
      System.out.println("initialized random values :");
      for (int i = 0; i < na.length; i++) {
         na[i] = (double) i + Math.random();
         System.out.format("%.4f ", na[i]);
      }
      System.out.println();
      CustomRecursiveAction task = new
         CustomRecursiveAction(na, 0, na.length);
      pool.invoke(task);
      System.out.println("Changed values :");
      for (inti = 0; i < 10; i++)
      System.out.format("%.4f ", na[i]);
      System.out.println();
   }
}

Заключение

Това е кратко описание на паралелното програмиране и как то се поддържа в Java. Добре установен факт е, че притежаването на N ядра няма да направи всичко N пъти по-бързо. Само част от приложенията на Java ефективно използват тази функция. Кодът за паралелно програмиране е трудна рамка. Освен това ефективните паралелни програми трябва да вземат предвид въпроси като балансиране на натоварването, комуникация между паралелни задачи и други подобни. Има някои алгоритми, които са по-подходящи за паралелно изпълнение, но много не. Във всеки случай Java API не е лишен от поддръжка. Винаги можем да се занимаваме с API, за да разберем кое е най-подходящо. Приятно кодиране 🙂


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да поръчам по две колони в SQL?

  2. Маскиране на данни в DB приложения

  3. Прилагане на полеви правила с помощта на класификация

  4. Компресия и нейните ефекти върху производителността

  5. Как да изчислим общия брой на бягане в червено изместване