Grand Central Dispatch ( GCD ) er Apples teknologi til at bygge applikationer, der udnytter multi-core processorer og andre SMP - systemer [1] . Denne teknologi er en implementering af opgaveparallelisme og er baseret på Thread Pool -designmønsteret . GCD blev først introduceret med Mac OS X 10.6 . Kildekoden til libdispatch- biblioteket , som implementerer GCD-tjenesterne, blev frigivet under Apache-licensen den 10. september 2009. [ 1] Arkiveret den 2. november 2009 på Wayback Machine . Efterfølgende blev biblioteket porteret [2] til et andet FreeBSD -operativsystem [3] .
GCD giver dig mulighed for at definere opgaver i en applikation, der kan udføres parallelt, og kører dem, når der er ledige computerressourcer ( processorkerner ) [4] .
En opgave kan defineres som en funktion eller som en " blok ". [5] En blok er en ikke-standard syntaksudvidelse af C / C ++ / Objective-C programmeringssprogene, der indkapsler kode og data i et enkelt objekt, analogt med en lukning . [fire]
Grand Central Dispatch bruger tråde på et lavt niveau, men skjuler implementeringsdetaljer fra programmøren. GCD-opgaver er lette, billige at oprette og skifte; Apple hævder, at tilføjelse af en opgave til køen kun kræver 15 processorinstruktioner , mens oprettelse af en traditionel tråd koster flere hundrede instruktioner. [fire]
En GCD-opgave kan bruges til at oprette et arbejdsemne, der er placeret i en opgavekø eller kan være bundet til en hændelseskilde. I det andet tilfælde, når hændelsen udløses, føjes opgaven til den relevante kø. Apple hævder, at denne mulighed er mere effektiv end at oprette en separat tråd, der venter på, at begivenheden udløses.
GCD-rammen erklærer flere datatyper og funktioner til at skabe og manipulere dem.
To eksempler, der demonstrerer brugervenligheden af Grand Central Dispatch, kan findes i John Syracuse's Snow Leopard anmeldelse på Ars Technica . [6] .
I første omgang har vi en applikation med analyseDokumentmetode, der tæller ord og afsnit i et dokument. Normalt er processen med at tælle ord og afsnit hurtigt nok og kan udføres på hovedtråden uden frygt for, at brugeren vil bemærke en forsinkelse mellem at trykke på knappen og få resultatet:
- ( IBAction ) analyseDokument: ( NSButton * ) sender { NSDictionary * stats = [ myDoc analyse ]; [ myModel setDict : statistik ]; [ myStatsView setNeedsDisplay : YES ]; }Hvis dokumentet er meget stort, så kan analysen tage ret lang tid for brugeren at bemærke, at applikationen "hænger". Følgende eksempel gør det nemt at løse dette problem:
- ( IBAction ) analyseDocument :( NSButton * ) sender { dispatch_async ( dispatch_get_global_queue ( 0 , 0 ), ^ { NSDictionary * stats = [ myDoc analyse ]; dispatch_async ( dispatch_get_main_queue (), ^ { [ myModel setDict : statistik ]; [ myStatsView setNeedsDisplay : YES ]; }); }); }Her placeres [myDoc analyse]-kaldet i en blok, som derefter placeres i en af de globale køer. Efter at [myDoc analyse] er fuldført, placeres en ny blok på hovedkøen, som opdaterer brugergrænsefladen . Ved at foretage disse enkle ændringer undgik programmøren det potentielle hængende i applikationen ved parsing af store dokumenter.
Det andet eksempel viser paralleliseringen af løkken:
for ( i = 0 ; i < tælle ; i ++ ) { resultater [ i ] = udføre_arbejde ( data , i ); } total = opsummere ( resultater , tælle );Her kaldes do_work-funktionen tælletider , resultatet af dens i-te udførelse tildeles det i-te element i resultatarrayet, hvorefter resultaterne summeres. Der er ingen grund til at tro, at do_works er afhængig af resultaterne af tidligere opkald til det, så der er intet til at stoppe med at udføre flere opkald til do_works parallelt. Følgende liste viser implementeringen af denne idé ved hjælp af GCD:
dispatch_apply ( count , dispatch_get_global_queue ( 0 , 0 ), ^ ( size_t i ){ resultater [ i ] = udføre_arbejde ( data , i ); }); total = opsummere ( resultater , tælle );I dette eksempel kører dispatch_apply tæller gange blokken blev overført til den, placerer hvert opkald i den globale kø og sender bloknumrene fra 0 til count-1. Dette gør det muligt for OS at vælge det optimale antal tråde for at få mest muligt ud af de tilgængelige hardwareressourcer. dispatch_apply vender ikke tilbage, før alle dens blokke er afsluttet, for at sikre, at alt det originale loops arbejde er afsluttet, før summary kaldes.
Udvikleren kan oprette en separat seriel kø for opgaver, der skal køre sekventielt, men kan køre på en separat tråd. En ny kø kan oprettes på denne måde:
dispatch_queue_t exampleQueue ; exampleQueue = dispatch_queue_create ( "com.example.unique.identifier" , NULL ); // exampleQueue kan bruges her. dispatch_release ( eksempelkø );Undgå at sætte en sådan opgave i en sekventiel kø, der sætter en anden opgave i samme kø. Dette vil med garanti resultere i en dødvande . Følgende liste viser et tilfælde af en sådan dødvande:
dispatch_queue_t exampleQueue = dispatch_queue_create ( "com.example.unique.identifier" , NULL ); dispatch_sync ( eksempelkø , ^ { dispatch_sync ( eksempelkø , ^ { printf ( "Jeg er nu fastlåst... \n " ); }); }); dispatch_release ( eksempelkø );