{{indexmenu_n>11}} ====== Lab 11 - Vícevláknové aplikace ====== * Pro vyučující: [[courses:bab36prga:internal:tutorialinstruction:11|]] {{ :courses:bab36prga:labs:prga-lab11-sources.zip |Výchozí soubory prga-lab11-sources.zip}} ===== Procvičovaná téma ===== * Vlákna (pthread) * Kritická sekce a zámky (mutex) * Synchronizace vláken - podmíněná proměnná (Conditional Variable) Cílem cvičení je seznámení se s vlákny, jejich implementací a vytvořením vícevláknové aplikace. Použití vláken je založeno na knihovně ''pthread'' (POSIX threads) a příklad demonstrujte použití základních konstrukcí ''pthread'' knihovny. ===== První vlákna ===== Vlákna mají datový typ ''pthread'' a vytvoří se zavoláním funkce [[https://linux.die.net/man/3/pthread_create|pthread_create]]. Každé vlákno má svůj úkol, tento úkol je zabalem do funkcí (např. ''routine''), které vlákno svým vytvořením invokuje. Vlákna mohou mít sdílená data, které jsou do výpočtu (funkce) předávána pomocí argumentů funkce. Vlákna se korektně ukončují pomocí [[https://linux.die.net/man/3/pthread_join|pthread_join]]. void* routine(void* args); ... pthread_t thread; pthread_create(&thread, NULL, routine, &args); pthread_join(thread, 0); ==== Úkoly ==== * Naimplementujte vlákno ''(void *print_thread (void *arg))'', které na standardní výstup vypisuje hodnotu parametru a hodnotu globální proměnné ''counter''. * Přidejte vlákno ''(void* compute_thread(void* arg))'', které bude měnit obsah globální proměnné sdílené mezi vlákny ''counter''. * Vytvořte vlákna voláním funkce ''pthread_create()'' s předáním ukazatele na příslušnou funkci ''compute_thread'', ''print_thread''. Přidejte korektní ukončení vláken. ===== Využití zámku (mutex) pro definování kritické sekce ===== V případě, kdy je nutné přistupovat k proměnné sdílené více vlákny je zpravidla žádoucí zabranit souběhu definováním kritické sekce zámkem (mutexem), který dovolí vstup do sekce pouze jedinému vláknu. Kritická sekce může být např. implementována s využitím zámku ''pthread_mutex_t''. Mutex se vytváří jako globální proměnná, která se inicializuje funkcí ''pthread_mutex_init()'' s výchozími atributy. pthread_mutex_t mtx; pthread_mutex_init(&mtx, NULL); Inicializaci mutexu se provádí před prvním použitím v příslušném vlákně. Je důležité si uvědomit, že vlákno může být spuštěno dříve, než je očekáváno (V podstatě spíše počítejte s tím, že vlákno je spuštěno ihned po vytvoření.) Přístupy ke globální proměnným (''counter'') se dávají do kritické sekce použitím ''pthead_mutex_lock()'' a ''pthread_mutex_unlock()''. void *routine(void *arg) { ... pthread_mutex_lock(&mtx); counter += 1; pthread_mutex_unlock(&mtx); ... } ==== Úkoly ==== * Vytvořte globální proměnnou ''mtx'' a umístěte ji do kritické sekce předchozího kódu. ===== Použití podmíněné proměnné (Conditional Variable) pro synchronizaci vláken ===== Synchronizaci vláken se může implementovat mechanismem podmíněných proměnných (conditional variable), tj. proměnná typu ''pthread_cond_t'', kterou použijeme k pozastavení vykonávání vlákna voláním [[https://linux.die.net/man/3/pthread_cond_wait|pthread_cond_wait]]. Takto čekající vlákno můžeme následně probudit voláním [[https://linux.die.net/man/3/pthread_cond_signal|pthread_cond_signal]] nebo [[https://linux.die.net/man/3/pthread_cond_broadcast|pthread_cond_broadcast]]. Používá se pokud práce vlákna závisí na změně v globální proměnné, např. aktualizace výpisů globální proměnné. (V některých případech může být práce vlákna velmi náročná a neefektivní. Například při aktualizace výpisu v cyklu ''output_thread'' je výpočetně velmi náročná, neboť cyklus je opakován tak rychle jak dovolují prostředky CPU. V našem případě je taková extermní frekvence aktualizace zbytečná a neefektivní (např. při běhu notebooku/tabletu na baterii se zbytečně čerpá omezená energie a navíc zařízení zahříváme).) == == Deklaruje se globální proměnná ''condvar'' pro podmíněnou proměnnou a inicializujte se výchozími parametry. pthread_cond_t condvar; ... pthread_cond_init(&condvar, NULL); Vykonávání vlákna může být pozastaveno voláním funkce ''pthread_cond_wait()'', která uvolní předaný (zamčený) zámek (mutex) a vyčká na signál podmíněné proměnné. Jakmile je signál přijat, je zahájeno znovu získání zámku (tj. pokud je zamčen, vlákno je pozastaveno a čeká na uvolnění tak jako při vstupu do kritické sekce) a následně pokračuje ve vykonávání. \\ Mějme dvě vlákna ''void *update_thread(void*)'' a ''void *output_thread(void*)'', které sdílí proměnnou ''counter''. V ''update_thread'' se proměnná updatuje a v ''output_thread'' se proměnná vypisuje. Tělo vlákna ''output_thread()'' aktualizujte pro použití podmíněné proměnné ''condvar'' a zámku ''mtx''. void* output_thread(void *d) { ... pthread_mutex_lock(&mtx); ... pthread_cond_wait(&condvar, &mtx); ... pthread_mutex_unlock(&mtx); ... Vláknu ''output_thread()'' pošleme signál voláním ''pthread_cond_signal()'' vždy když dojde k zvyšení hodnoty čítače ''counter''. void* update_thread(void *d) { ... pthread_mutex_lock(&mtx); counter += 1; ... pthread_cond_signal(&condvar); ... pthread_mutex_unlock(&mtx); ... == Úkoly == * Uvažte, zda do předchozího kódu je vhodné přidat podmíněnou proměnnou, pokud ano, kam? ===== Další úkoly na cvičení ===== V případě dostatku času pokračujete dle [[courses:bab36prga:labs:lab12|Lab 12 - Vícevláknové aplikace]] a implementujte aplikaci z přednášky [[courses:bab36prga:lectures:start#Vícevláknové programování, modely aplikací, POSIX vlákna C11 vlákna|přednášky 11]].