Cílem cvičení je implementovat program s třemi paralelně běžícími vlákny. První vlákno je určeno pro zpracování vstupu (čtení stiknuté klávesy), druhé vlákno aktualizuje výstup (jednořádkový) a třetí vlákno implementuje časovač, který po uplynutí definované periody zvýší hodnotu proměnné (čítače). Perioda může být nastavována uživatelem, stiskem definovaných kláves. Použití vláken je založeno na knihovně pthread
(POSIX threads) a příklad demonstruje použití základních konstrukcí pthread
knihovny.
void* thread1(void*); void* thread2(void*);
thread1()
implementujte cyklus, který inkrementuje global proměnnou counter
s periodou 100 ms, např.,
void* thread1(void *v) { bool q = false; /* from the <stdbool.h> */ while (!q) { usleep(100 * 1000); /* from the <unistd.h> */ counter += 1; } return 0; }
thread2()
implementujte aktualizaci jednořádkového výstupu s aktuální hodnotou proměnné counter
, např.
void* thread2(void *v) { bool q = false; while (!q) { printf(*"\rCounter %10i", counter); } return 0; }
pthread_create()
s předáním ukazatele na příslušnou funkci thread1()
a thread2()
např.
counter = 0; pthread_t thrs[2]; pthread_create(&thrs[0], NULL, thread1, NULL); pthread_create(&thrs[1], NULL, thread2, NULL);
Jakmile je vlákno vytvořeno, je spuštěno a začne vykonávat tělo předané funkce.
Enter
a následně vyčkejte ukončení všech vláken voláním pthread_join()
, např.
getchar(); for (int i = 0; i < 2; ++i) { pthread_join(thrs[i], NULL); }
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
.
mtx
, kterou inicializujte funkcí pthread_mutex_init()
s výchozími atributy, např.
pthread_mutex_t mtx; pthread_mutex_init(&mtx, NULL);
counter
dejte do kritické sekce použitím pthead_mutex_lock()
a pthread_mutex_unlock()
, např.
void *thread1(void *v) { ... pthread_mutex_lock(&mtx); counter += 1; pthread_mutex_unlock(&mtx); ... }
quit
k indikaci, že program bude ukončen jakmile uživatel stiskne klávesu Enter
např.
bool quit = false; ... gechar(); pthread_mutex_lock(&mtx); quit = true; pthread_mutex_unlock(&mtx); ...
quit
použijte pro ukončení smyčky ve funkci thread1()
i thread2()
, např.
void* thread1(void *v) { ... while (!q) { ... pthread_mutex_lock(&mtx); ... q = quit; ....
Aktuální implementace aktualizace výpisu v cyklu thread2
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). V podstatě nám stačí aktualizovat výpis pouze v případě, kdy dojde ke změně hodnoty proměnné counter
Takovou synchronizaci vláken můžeme 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_singnal()
nebo pthread_cond_broadcast()
.
condvar
pro podmíněnou proměnnou a inicializujte ji výchozími parametry, např.
pthread_cond_t condvar; ... pthread_cond_init(&condvar, NULL);
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í. Tělo vlákna thread2()
aktualizujte pro použití podmíněné proměnné condvar
a zámku mtx
.
void* thread2(void *d) { ... pthread_mutex_lock(&mtx); ... pthread_cond_wait(&condvar, &mtx); ... pthread_mutex_unlock(&mtx); ...
thread2()
pošleme signál voláním pthread_cond_signal()
vždy když dojde k zvyčení hodnoty čítače counter
void* thread1(void *d) { ... pthread_mutex_lock(&mtx); counter += 1; ... pthread_cond_signal(&condvar); ... pthread_mutex_unlock(&mtx); ...
flush
na stdout
ve výstupním vlákně, např. jako fflush(stdout);
main()
funkce
Enter
přepněte terminál do raw
režimu, například tak jak bylo prezentována v přednášce lec08
"\rAlarm period: %10i Alarm counter: %10i"
V další části cvičení pokračujte implementací domácího úkolu hw09.