2

Paralelná dekompozícia v kotlinovských korutinách

 8 months ago
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.
neoserver,ios ssh client

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 Deferred reprezentujúci úlohu.

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 coroutineScope plní dva účely:

  1. Pozastaví sa (suspend), kým korutiny, ktoré sú v ňom deklarované, nedobehnú.

  2. Ak tento scope zlyhá - či kvôli zrušeniu (cancel) alebo výnimke — všetky vnorené korutiny sa tiež zrušia.

Supervisor Scope

Supervízorský scope je podobný ako coroutineScope, ale rušenie potomkov prebieha len smerom „od rodiča k potomkom“, nikdy nie naopak.

  1. Ak ktorýkoľvek potomok zlyhá, scope sa nezruší.

  2. Pozastaví sa (suspend), kým korutiny, ktoré sú v ňom deklarované, nedobehnú.

  3. 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.

>> Home


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK