{{indexmenu_n>8}} ====== 8 - Vícevláknové aplikace ====== * Vlákna (pthread) * Kritická sekce a zámky (mutex) * Synchronizace vláken - podmíněná proměnná (Conditional Variable) * pro vyučující: [[courses:bab36prga:internal:tutorialinstruction:08|]] 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. ===== Výuka ===== {{ :courses:bab36prga:labs:prga_lab09.zip | Zdrojové kódy a šablona pro cvičení}} [[https://docs.google.com/presentation/d/1W8tIfvMAXJEAQVuYIG3dbKFJjgCdqfrUk11TxzYBiJ8/edit?usp=sharing|Prezentace pro cvičení ]] ===== 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 inkovuje. 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, ¶maters); 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?