Paralelná dekompozícia v kotlinovských korutinách
source link: https://novotnyr.github.io/scrolls/kotlin-koroutiny-parallel-decomposition/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Ukážme si delenie úlohy na paralelné výpočty s použitím kotlinovských korutín.
Počítajme slová v samostatných korutinách bežiacich paralelne a spočítavajme celkové výsledky§
Urobme si funkciu na počítanie slov v súbore:
Ak chceme počítať súbory v adresári, vytvorme si pomocnú funkciu:
Sekvenčné počítanie
Sekvenčné počítanie je jednoduché:
Čo takto však rýchlejšie paralelné výpočty?
Paralelné výpočty
Paralelné spustenie potrebuje najmä:
-
spustenie úlohy v sekcii
async
, -
získanie objektu s úlohou
Deferred
, -
vyčkanie na dobehnutie úloh pomocou a získanie výsledkov cez
awaitAll
, -
spravovanie chýb cez
coroutineScope
1 | Výpočet pustíme v samostatnej korutine na pozadí.
Keďže ide o vstupno-výstupnú operáciu, použijeme dispečera pre I/O.
Výsledkom je objekt |
2 | Vyčkáme na dobehnutie všetkých úloh v korutinách. |
3 | Ustanovíme záber (scope) korutín. Ak ktorákoľvek z úloh zlyhá s výnimkou, všetky ostatné korutiny pre výpočet počtov slov sa zrušia tiež. Zároveň zabezpečíme, že tento scope dobehne až keď dobehnú korutiny, ktoré sa v ňom spustia. |
Objekt Deferred reprezentuje „budúci výsledok“, či „hodnotu, ktorá sa eventuálne vypočíta“. Ke filozoficky podobný CompletableFuture (Java), či Promise (JavaScript).
|
Ak to chceme vyskúšať v main
-e, musíme použiť runBlocking
:
Ak chceme byť viac funkcionálni a zbaviť sa premennej jobs
:
Filozofia async -awaitAll skôr pripomína filozofiu „fork-join“ či „map-reduceH, kde sa v bloku async spustia paralelné úlohy a v bloku awaitAll pozbierajú výsledky.
|
+
Sekcia coroutineScope
dbá na to, aby v prípade chýb sa celá operácia rovno zrušila.
Kombinácia coroutineScope
, async
a awaitAll
umožňuje štruktúrovanú konkurentnosť (structured concurrency).
-
Nové korutiny sa spúšťajú v rámci konkrétneho scope, ktorý určuje ich životnosť.
-
Dbá sa na to, aby beh korutiny „neunikol“ mimo životnosti rodiča.
-
Výnimky sa korektne spracujú tak, aby sa nenarušili životnosti korutín a rodičovského scopu.
Výnimky a korutiny
Predstavme si, že chceme počítať slová nad zoznamom súborov, kde jeden z nich nejestvuje:
Obratom uvidíme výnimku:
V prípade výnimky v ktorejkoľvek z korutín spúšťaných v async
bloku sa ostatné súrodenecké korutiny zrušia.
Nezabudnime, že
|
Supervisor Scope
Supervízorský scope je podobný ako coroutineScope
, ale rušenie potomkov prebieha len smerom „od rodiča k potomkom“, nikdy nie naopak.
-
Ak ktorýkoľvek potomok zlyhá, scope sa nezruší.
-
Pozastaví sa (suspend), kým korutiny, ktoré sú v ňom deklarované, nedobehnú.
-
Ak scope zlyhá - či kvôli zrušeniu (cancel) alebo výnimke — všetky vnorené korutiny sa tiež zrušia.
Vyčkávanie na dobehnutie potomkov
Na rozdiel od coroutineScope
nie je vhodné používať awaitAll
, ktorý skončí v momente, keď ktorýkoľvek z potomkov zlyhá.
To sme videli vo výnimke hore.
Namiesto toho budeme vyčkávať jednotlivo a ošetrovať prípadné výnimky, ktoré korutiny vyhodia.
Každý výsledok volania async
typu Deferred
vyčkáme pomocou samostatného volania await
.
Toto volanie buď uspeje a vráti výsledok — teda počet slov v súbore — alebo zlyhá s výnimkou, ktorú odchytíme a vhodne spracujeme.
1 | Prúd objektov Deferred postupne spracujeme po jednom. |
2 | Na každý Deferred vyčkáme cez await . |
3 | Ak nastane výnimka, odchytíme ju a vrátime null > |
Výsledkom bude zoznam, kde niektoré prvky budú obsahovať počet slov v súbore a pre nedostupné veľkosti kvôli výnimkám bude v zozname null
.
[796494, null]
Bloky runBlocking
a coroutineScope
Blok runBlocking
je coroutine builder, teda nástroj na zostrojenie a spustenie korutiny.
Jeho jediné použitie je pri premostení sveta bežného programovania a sveta, v ktorom je možné spúšťať suspend
funkcie — typicky len v metóde main
, historických knižniciach a testoch.
coroutineScope
sa používa len vo svete suspend
funkcií. (Samotná funkcia coroutineScope
je označená ako suspend
). Tento blok nevytvára a nespúšťa novú korutinu.
Oba bloky počkajú na dobehnutie potomkovských korutín, ale runBlocking
pri tom zablokuje vlákno v ktorom beží, zatiaľ čo coroutineScope
sa pozastaví (suspenduje) bez blokovania.
Oba bloky riešia obojsmerné rušenie: ak zlyhá potomok, zrušia sa aj všetky ostatné potomkovské korutiny a zároveň aj príslušný rodičovský scope.
Priama kombinácia runBlocking a coroutineScope nedáva dohromady zmysel, keďže coroutineScope vyčká na dobehnutie korutín v async a runBlocking tiež počká na dobehnutie tých istých korutín.
To je však špeciálna situácia v hračkárskych a tutoriálových textoch.
|
Jednoduchý, ale výhradne tutoriálový príklad spustí rovno korutinu v dispečeri pre vstupno-výstupné operácie, počítacie korutiny sa spustia cez async
v tom istom dispečeri a vyčkávanie na dobehnutie korutín (spolu s vyblokovaním hlavného vlákna) zabezpečí runBlocking
.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK