sql >> Databasteknik >  >> RDS >> Database

Grundläggande om parallellprogrammering med Fork/Join Framework i Java

Med tillkomsten av flerkärniga processorer de senaste åren är parallell programmering sättet att dra full nytta av de nya bearbetningsarbetshästarna. Parallell programmering hänvisar till samtidig exekvering av processer på grund av tillgängligheten av flera bearbetningskärnor. Detta leder i huvudsak till en enorm ökning av programmens prestanda och effektivitet i motsats till linjär exekvering av en kärna eller till och med multithreading. Fork/Join-ramverket är en del av Java concurrency API. Detta ramverk gör det möjligt för programmerare att parallellisera algoritmer. Den här artikeln utforskar konceptet med parallell programmering med hjälp av Fork/Join Framework som finns i Java.

En översikt

Parallell programmering har en mycket bredare klang och är utan tvekan ett stort område att utveckla på några rader. Kärnan i saken är ganska enkel, men operativt mycket svårare att uppnå. Enkelt uttryckt innebär parallell programmering att skriva program som använder mer än en processor för att slutföra en uppgift, det är allt! Gissa vad; det låter bekant, eller hur? Det rimmar nästan med tanken på multithreading. Men observera att det finns några viktiga skillnader mellan dem. På ytan är de lika, men underströmmen är helt annorlunda. Faktum är att multithreading introducerades för att ge en sorts illusion av parallell bearbetning utan någon egentlig parallell exekvering alls. Vad multithreading verkligen gör är att den stjäl CPU-tomtid och använder den till sin fördel.

Kort sagt, multithreading är en samling av diskreta logiska enheter av uppgifter som körs för att ta sin del av CPU-tiden medan en annan tråd tillfälligt väntar på, till exempel, lite användarinput. Den inaktiva CPU-tiden delas optimalt mellan konkurrerande trådar. Om det bara finns en CPU är den tidsdelad. Om det finns flera CPU-kärnor är de också delade hela tiden. Därför pressar ett optimalt flertrådigt program ut CPU:ns prestanda genom den smarta mekanismen för tidsdelning. I huvudsak är det alltid en tråd som använder en CPU medan en annan tråd väntar. Detta sker på ett subtilt sätt att användaren får en känsla av parallell bearbetning där bearbetningen i verkligheten faktiskt sker i snabb följd. Den största fördelen med multithreading är att det är en teknik för att få ut det mesta av bearbetningsresurserna. Nu är den här idén ganska användbar och kan användas i alla miljöer, oavsett om den har en enda processor eller flera processorer. Tanken är densamma.

Parallell programmering, å andra sidan, innebär att det finns flera dedikerade CPU:er som utnyttjas parallellt av programmeraren. Denna typ av programmering är optimerad för en multicore CPU-miljö. De flesta av dagens maskiner använder flerkärniga processorer. Därför är parallell programmering ganska relevant nuförtiden. Även den billigaste maskinen är monterad med flerkärniga processorer. Titta på de handhållna enheterna; även de är multicore. Även om allt verkar skumt med flerkärniga processorer, här är en annan sida av historien också. Betyder fler CPU-kärnor snabbare eller effektivare datoranvändning? Inte alltid! Den giriga filosofin "ju fler desto roligare" gäller inte för datorer, inte heller i livet. Men de finns där, oövervinnerligt – dual, quad, octa och så vidare. De är där mest för att vi vill ha dem och inte för att vi behöver dem, åtminstone i de flesta fall. I verkligheten är det relativt svårt att hålla ens en enda CPU upptagen i daglig datoranvändning. Flera kärnor har dock sina användningsområden under speciella omständigheter, såsom i servrar, spel och så vidare, eller för att lösa stora problem. Problemet med att ha flera processorer är att det kräver minne som måste matcha hastigheten med processorkraften, tillsammans med blixtsnabba datakanaler och andra tillbehör. Kort sagt, flera CPU-kärnor i daglig datoranvändning ger prestandaförbättringar som inte kan uppväga mängden resurser som behövs för att använda den. Följaktligen får vi en underutnyttjad dyr maskin, kanske bara avsedd att visas upp.

Parallell programmering

Till skillnad från multithreading, där varje uppgift är en diskret logisk enhet av en större uppgift, är parallella programmeringsuppgifter oberoende och deras exekveringsordning spelar ingen roll. Uppgifterna definieras enligt den funktion de utför eller data som används vid bearbetning; detta kallas funktionell parallellism eller dataparallellism , respektive. I funktionell parallellism arbetar varje processor på sin del av problemet medan i dataparallellism arbetar processorn på sin del av datan. Parallell programmering är lämplig för en större problembas som inte passar in i en enda CPU-arkitektur, eller så kan det vara så att problemet är så stort att det inte kan lösas inom en rimlig tidsuppskattning. Som ett resultat kan uppgifter, när de är fördelade mellan processorer, få resultatet relativt snabbt.

The Fork/Join Framework

Fork/Join Framework definieras i java.util.concurrent paket. Den innehåller flera klasser och gränssnitt som stöder parallell programmering. Vad den i första hand gör är att den förenklar processen för att skapa flera trådar, deras användningsområden och automatiserar mekanismen för processallokering mellan flera processorer. Den anmärkningsvärda skillnaden mellan multithreading och parallell programmering med detta ramverk är mycket lik det vi nämnde tidigare. Här är bearbetningsdelen optimerad för att använda flera processorer till skillnad från multithreading, där vilotiden för den enda CPU:n optimeras på basis av delad tid. Den extra fördelen med detta ramverk är att använda multithreading i en parallell exekveringsmiljö. Ingen skada där.

Det finns fyra kärnklasser i detta ramverk:

  • ForkJoinTask: Detta är en abstrakt klass som definierar en uppgift. Vanligtvis skapas en uppgift med hjälp av fork() metod definierad i denna klass. Den här uppgiften liknar nästan en vanlig tråd skapad med Tråden klass, men är lättare än den. Mekanismen den tillämpar är att den möjliggör hantering av ett stort antal uppgifter med hjälp av ett litet antal faktiska trådar som går med i ForkJoinPool . gaffeln() metoden möjliggör asynkron exekvering av den anropande uppgiften. join() metoden gör det möjligt att vänta tills uppgiften som den anropas slutligen avslutas. Det finns en annan metod, kallad invoke() , som kombinerar gaffeln och gå med operationer i ett enda samtal.
  • ForkJoinPool: Den här klassen tillhandahåller en gemensam pool för att hantera exekvering av ForkJoinTask uppgifter. Det ger i princip ingångspunkten för inlämningar från icke-ForkJoinTask kunder, samt förvaltning och övervakning.
  • Rekursiv åtgärd: Detta är också en abstrakt förlängning av ForkJoinTask klass. Vanligtvis utökar vi den här klassen för att skapa en uppgift som inte returnerar ett resultat eller har ett tomt returtyp. compute() metod som definieras i den här klassen åsidosätts för att inkludera beräkningskod för uppgiften.
  • Rekursiv uppgift: Detta är en annan abstrakt förlängning av ForkJoinTask klass. Vi utökar den här klassen för att skapa en uppgift som returnerar ett resultat. Och i likhet med ResursiveAction innehåller den också en skyddad abstrakt compute() metod. Denna metod åsidosätts för att inkludera beräkningsdelen av uppgiften.

The Fork/Join Framework Strategy

Detta ramverk använder en rekursiv dela-och-härska strategi för att implementera parallell bearbetning. Den delar i princip upp en uppgift i mindre deluppgifter; sedan delas varje deluppgift ytterligare in i delunderuppgifter. Denna process tillämpas rekursivt på varje uppgift tills den är tillräckligt liten för att hanteras sekventiellt. Anta att vi ska öka värdena för en array av N tal. Detta är uppgiften. Nu kan vi dela upp arrayen med två och skapa två deluppgifter. Dela upp var och en av dem igen i ytterligare två deluppgifter, och så vidare. På så sätt kan vi tillämpa en dela-och-härska strategi rekursivt tills uppgifterna pekas ut till ett enhetsproblem. Detta enhetsproblem kan sedan exekveras parallellt av de multipla kärnprocessorerna som finns tillgängliga. I en icke-parallell miljö, vad vi var tvungna att göra är att cykla igenom hela arrayen och göra bearbetningen i sekvens. Detta är helt klart ett ineffektivt tillvägagångssätt med tanke på parallell bearbetning. Men den verkliga frågan är kan alla problem delas och erövras ? Definitivt inte! Men det finns problem som ofta involverar någon sorts array, insamling, av gruppering av data som särskilt passar detta tillvägagångssätt. Förresten, det finns problem som kanske inte använder insamling av data men kan optimeras för att använda strategin för parallell programmering. Vilken typ av beräkningsproblem som är lämpliga för parallell bearbetning eller diskussion om parallellalgoritm faller utanför ramen för denna artikel. Låt oss se ett snabbt exempel på tillämpningen av Fork/Join Framework.

Ett snabbt exempel

Detta är ett mycket enkelt exempel för att ge dig en idé om hur man implementerar parallellism i Java med Fork/Join Framework.

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();
   }
}

Slutsats

Detta är en kortfattad beskrivning av parallell programmering och hur det stöds i Java. Det är ett väletablerat faktum att ha N kärnor kommer inte att göra allt till N gånger snabbare. Endast en del av Java-applikationer använder den här funktionen effektivt. Parallell programmeringskod är en svår ram. Dessutom måste effektiva parallellprogram beakta frågor som lastbalansering, kommunikation mellan parallella uppgifter och liknande. Det finns några algoritmer som bättre passar parallellt exekvering men många gör det inte. I vilket fall som helst saknar inte Java API sitt stöd. Vi kan alltid mixtra med API:erna för att ta reda på vad som passar bäst. Lycka till med kodningen 🙂


  1. Hur man ansluter till MySQL Server efter installation av XAMPP på Mac OS

  2. Minimera effekten av att bredda en IDENTITY-kolumn – del 3

  3. UI-designmönster som inte skalas

  4. Hur NVL() fungerar i MariaDB