Lab 11 - Vícevláknové aplikace

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 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í pthread_join.

void* routine(void* args);
...
pthread_t thread;
pthread_create(&thread, NULL, routine, &paramaters);
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 pthread_cond_wait. Takto čekající vlákno můžeme následně probudit voláním pthread_cond_signal nebo 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 Lab 12 - Vícevláknové aplikace a implementujte aplikaci z přednášky přednášky 11.

courses/bab36prga/labs/lab11.txt · Last modified: 2024/02/14 09:38 by faiglj