Differences

This shows you the differences between two versions of the page.

Link to this comparison view

sde:laboratoare:09_ro_python [2020/04/14 13:57]
ioana_maria.culic
— (current)
Line 1: Line 1:
-====== Laborator 9 - Sincronizarea Thread-urilor ====== 
  
-===== Prezentare teoretică ===== 
- 
-Pentru sincronizarea firelor de execuție, avem la dispoziție:​ 
-  *[[#​mutex|mutex]] 
-  *[[#​semafor|semafoare]] 
-  *[[#​variabile conditie|variabile de condiție]] 
-  *[[#​bariera|bariere]] 
- 
-===== Mutex ===== 
- 
-Mutex-urile (mutual exclusion locks) sunt obiecte de sincronizare utilizate pentru a asigura **accesul exclusiv** într-o secțiune de cod în care se utilizează **date partajate** între două sau mai multe fire de execuție. Un mutex are două stări posibile: **ocupat** și **liber**. Un mutex poate fi ocupat de un **singur fir** de execuție la un moment dat. Atunci când un mutex este ocupat de un fir de execuție, el nu mai poate fi ocupat de niciun alt fir. În acest caz, o cerere de ocupare venită din partea unui alt fir, în general, va **bloca** firul până în momentul în care mutex-ul devine liber. 
- 
-==== Crearea unui mutex ==== 
- 
-În Python, mutex-ul este reprezentat prin clasa [[https://​docs.python.org/​3/​library/​threading.html#​threading.Lock|threading.Lock]]. La creare mutex-ul este in starea **unlocked**. Odată ocupat de un thread, orice alt apel de ocupare a mutex-ului va eșua. 
- 
-=== Mutex-uri recursive === 
- 
-Folosind clasa [[https://​docs.python.org/​3/​library/​threading.html#​threading.RLock|threading.RLock]],​ putem crea un mutex recursiv. Acest mutex poate fi ocupat de mai multe ori de același thread. Pentru a fi eliberat către un alt thread, thread-ul care a ocupat mutex-ul trebuie să apeleze funcția de eliberare de un număr egal de ori cu numărul de apeluri a funcției de ocupare a mutex-ului. ​ 
- 
-==== Ocuparea/​eliberarea unui mutex ==== 
- 
-Funcțiile de ocupare/​eliberare a unui mutex ([[https://​docs.python.org/​3/​library/​threading.html#​threading.Lock.acquire|acquire]],​ [[https://​docs.python.org/​3/​library/​threading.html#​threading.Lock.release|release]]):​ 
- 
-<code python> 
-mutex.acquire (blocking=True,​ timeout=-1) 
-mutex.release() 
-</​code>​ 
- 
-Dacă mutex-ul este **liber** în momentul apelului, acesta va fi ocupat de firul apelant și funcția va întoarce imediat. Dacă mutex-ul este ocupat de un **alt fir**, comportamentul funcției depinde de parametrul **blockling**:​ 
-  * ''​True''​ - thread-ul se va bloca până mutex-ul este eliberat; dacă parametrul **timeout** are o valoare pozitivă, thread-ul se va bloca doar pentru **timeout** secunde (în acest caz funcția va întoarce **True** dacă mutex-ul a fost ocupat și **False** dacă nu); 
-  * ''​False''​ - funcția întoarce imediat valoarea **True** dacă funcția **acquire** a funcționat și valoarea **False** dacă apelul a eșuat (mutex-ul este ocupat de un alt fir de execuție). 
- 
-Nu este garantată o ordine FIFO de ocupare a unui mutex. **Oricare din firele** aflate în așteptare la deblocarea unui mutex pot să-l acapareze. 
- 
-Pentru a elibera un mutex, este necesară apelarea funcției **release**. 
- 
-<note info> 
-Dacă se apelează funcția **release** pe un mutex **liber**, funcția va arunca o excepție. 
-</​note>​ 
- 
-==== Exemplu de utilizare a mutex-urilor ==== 
- 
-Un exemplu de utilizare a unui mutex pentru a serializa accesul la variabila globală ''​global_counter'':​ 
- 
-<code python> 
-import threading 
-  
-NUM_THREADS = 5 
-  
-# global mutex 
-mutex = threading.Lock() 
- 
-global_counter = 0 
-def thread_routine(): ​ 
-    global global_counter 
-    ​ 
-    thread_id = threading.get_ident() 
-    # acquire global mutex 
-    mutex.acquire() 
-  
-    # print and modify global_counter 
-    print("​Thread {} says global_counter={}"​.format (thread_id, global_counter)) 
-    global_counter = global_counter + 1 
-  
-    # release mutex - now other threads can modify global_counter 
-    mutex.release() 
- 
-threads = [] 
-# all threads execute thread_routine 
-for i in range (NUM_THREADS):​ 
-    t = threading.Thread (target=thread_routine) 
-    threads.append (t) 
-    t.start() 
- 
-for i in range (NUM_THREADS):​ 
-    t.join() 
-</​code>​ 
- 
-<code bash> 
-$ python3 example.py ​ 
-Thread 123145557700608 says global_counter=0 
-Thread 123145562955776 says global_counter=1 
-Thread 123145557700608 says global_counter=2 
-Thread 123145568210944 says global_counter=3 
-Thread 123145562955776 says global_counter=4 
-</​code>​ 
- 
-===== Semafor ===== 
- 
-Semafoarele sunt obiecte de sincronizare ce reprezintă o generalizare a mutex-urilor prin aceea că **salvează numărul de operații de eliberare** (incrementare) efectuate asupra lor. Practic, un semafor reprezintă un întreg care se incrementează/​decrementează atomic. Valoarea unui semafor nu poate scădea sub 0. Dacă semaforul are valoarea 0, operația de decrementare se va bloca până când valoarea semaforului devine strict pozitivă. Mutex-urile pot fi privite, așadar, ca niște semafoare binare. 
- 
-În Python, semafoarele sunt reprezentate de clasa [[https://​docs.python.org/​3/​library/​threading.html#​threading.Semaphore|Semaphore]]:​ 
-<code python> 
-import threading 
-semaphore = threading.Semaphore (value=1) 
-</​code>​ 
-  
-==== Operații pe semafoare ==== 
- 
-  * ocuparea semforului ([[https://​docs.python.org/​3/​library/​threading.html#​threading.Semaphore.acquire|acquire]]) - decrementează contorul semaforului;​ 
-  * eliberarea semaforului ([[https://​docs.python.org/​3/​library/​threading.html#​threading.Semaphore.release|release]]) - incrementeaza contorul semaforului. 
- 
-Funcția [[https://​docs.python.org/​3/​library/​threading.html#​threading.Semaphore.acquire|acquire]] are un comportament similar cu cel al funcție **acquire** specifice mutex-ului. Diferența este că în acest caz, la apelul **acuire** se verifică dacă contorul semaforului este mai mare ca 0.  
- 
-==== Semafor limitat ==== 
- 
-Pe lângă clasa **Semaphore**,​ modulul **threading** exportă clasa [[https://​docs.python.org/​3/​library/​threading.html#​threading.BoundedSemaphore|BoundedSemaphore]] care se asigură că funcția **release** nu este apelată de mai multe ori decât funcția **acquire**. Dacă apelul funcției **release** va rezulta într-o creștere a contorului mai mare decât valoarea inițială, apelul funcției va rezulta într-o eroare. 
- 
- 
-==== Exemplu de utilizare semafor==== 
-<code python> 
-import threading 
-  
-NUM_THREADS = 5 
-  
-# global semaphore 
-my_sem = threading.Semaphore(value=1) 
- 
-global_counter = 0 
-def thread_routine(): ​ 
-    global global_counter 
-    ​ 
-    thread_id = threading.get_ident() 
-    # acquire global mutex 
-    my_sem.acquire() 
-  
-    # print and modify global_counter 
-    print("​Thread {} says global_counter={}"​.format (thread_id, global_counter)) 
-    global_counter = global_counter + 1 
-  
-    # release mutex - now other threads can modify global_counter 
-    my_sem.release() 
- 
-threads = [] 
-# all threads execute thread_routine 
-for i in range (NUM_THREADS):​ 
-    t = threading.Thread (target=thread_routine) 
-    threads.append (t) 
-    t.start() 
- 
-for i in range (NUM_THREADS):​ 
-    t.join() 
-</​code>​ 
- 
-===== Variabile condiție ===== 
- 
-Variabilele condiție pun la dispoziție un sistem de notificare pentru fire de execuție, permițându-i unui fir să se blocheze în așteptarea unui semnal din partea unui alt fir. Folosirea corectă a variabilelor condiție presupune un protocol cooperativ între firele de execuție. 
- 
-Mutex-urile și semafoarele permit blocarea **altor fire** de execuție. Variabilele de condiție se folosesc pentru a bloca **firul curent** până la îndeplinirea unei condiții. 
- 
-Variabilele condiție sunt obiecte de sincronizare care-i permit unui fir de execuție să-și suspende execuția până când o condiție (predicat logic) **devine adevărată**. Când un fir de execuție determină că predicatul a devenit adevărat, va semnala variabila condiție, deblocând astfel unul sau toate firele de execuție blocate la acea variabilă condiție (în funcție de intenție). 
- 
-O variabilă condiție trebuie întotdeauna folosită **împreună cu un mutex** pentru evitarea race-ului care se produce când un fir se pregătește să aștepte la variabila condiție în urma evaluării predicatului logic, iar alt fir semnalizează variabila condiție chiar înainte ca primul fir să se blocheze, pierzându-se astfel semnalul. Așadar, operațiile de semnalizare,​ testare a condiției logice și blocare la variabila condiție trebuie efectuate având **ocupat** mutex-ul asociat variabilei condiție. Condiția logică este testată sub protecția mutex-ului, iar dacă nu este îndeplinită,​ firul apelant se blochează la variabila condiție, eliberând atomic mutex-ul. În momentul deblocării,​ un fir de execuție va încerca să ocupe mutex-ul asociat variabilei condiție. De asemenea, testarea predicatului logic trebuie făcută într-o **buclă**, deoarece, dacă sunt eliberate mai multe fire deodată, doar unul va reuși să ocupe mutex-ul asociat condiției. Restul vor aștepta ca acesta să-l elibereze, însă este posibil ca firul care a ocupat mutex-ul să **schimbe** valoarea predicatului logic pe durata deținerii mutex-ului. Din acest motiv celelalte fire trebuie să testeze din nou predicatul pentru că, altfel, și-ar începe execuția presupunând predicatul adevărat, când el este, de fapt, fals. 
- 
-==== Inițializarea/​distrugerea unei variabile de condiție ==== 
- 
-Inițializarea unei variabile de condiție se face folosind macro-ul PTHREAD_COND_INITIALIZER sau funcția [[http://​linux.die.net/​man/​3/​pthread_cond_init|pthread_cond_init]]. Distrugerea unei variabile de condiție se face prin funcția [[http://​linux.die.net/​man/​3/​pthread_cond_destroy|pthread_cond_destroy]]. 
- 
-<code c> 
-// inițializare statică a unei variabile de condiție cu atribute implicite 
-// NB: variabila de condiție nu este eliberată, ​ 
-//     ​durata de viață a variabilei de condiție este durata de viață a programului. 
-pthread_cond_t cond = PTHREAD_COND_INITIALIZER;​ 
-  
-// semnăturile funcțiilor de inițializare și eliberare de variabile de condiție: 
-int pthread_cond_init ​  ​(pthread_cond_t *cond, pthread_condattr_t *attr); 
-int pthread_cond_destroy(pthread_cond_t *cond); 
-</​code>​ 
- 
-Ca și la mutex-uri: 
-  *dacă parametrul ''​attr''​ este NULL, se folosesc atribute implicite 
-  *trebuie să nu existe nici un fir de execuție în așteptare pe variabila de condiție atunci când aceasta este distrusă, altfel se întoarce ''​EBUSY''​. ​ 
-==== Blocarea la o variabilă condiție ==== 
- 
-Pentru a-și suspenda execuția și a aștepta la o variabilă condiție, un fir de execuție va apela funcția [[http://​linux.die.net/​man/​3/​pthread_cond_wait|pthread_cond_wait]]:​ 
- 
-<code c> 
-int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 
-</​code>​ 
- 
-Firul de execuție apelant trebuie să fi **ocupat** deja mutex-ul asociat, în momentul apelului. Funcția ''​pthread_cond_wait''​ va **elibera** mutex-ul și se va **bloca**, așteptând ca variabila condiție să fie **semnalizată** de un alt fir de execuție. Cele două operații sunt efectuate **atomic**. În momentul în care variabila condiție este semnalizată,​ se va încerca ocuparea mutex-ului asociat, și după **ocuparea** acestuia, apelul funcției va întoarce. Observați că firul de execuție apelant poate fi suspendat, după deblocare, în așteptarea ocupării mutex-ului asociat, timp în care predicatul logic, adevărat în momentul deblocării firului, poate fi modificat de alte fire. De aceea, apelul ''​pthread_cond_wait''​ trebuie efectuat într-o buclă în care se testează valoarea de adevăr a predicatului logic asociat variabilei condiție, pentru a asigura o serializare corectă a firelor de execuție. Un alt argument pentru testarea în buclă a predicatului logic este acela că un apel ''​pthread_cond_wait''​ poate fi **întrerupt** de un semnal asincron (vezi laboratorul de semnale), înainte ca predicatul logic să devină adevărat. Dacă firele de execuție care așteptau la variabila condiție nu ar testa din nou predicatul logic, și-ar continua execuția presupunând greșit că acesta e adevărat. 
-==== Blocarea la o variabilă condiție cu timeout ==== 
- 
-Pentru a-și suspenda execuția și a aștepta la o variabilă condiție, nu mai târziu de un moment specificat de timp, un fir de execuție va apela [[http://​linux.die.net/​man/​3/​pthread_cond_timedwait|pthread_cond_timedwait]]:​ 
- 
-<code c> 
-int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, ​ 
-                           const struct timespec *abstime); 
-</​code>​ 
- 
-Funcția se comportă la fel ca ''​pthread_cond_wait'',​ cu excepția faptului că, dacă variabila condiție nu este semnalizată mai devreme de ''​abstime'',​ firul apelant este deblocat, și, după ocuparea mutex-ului asociat, funcția se întoarce cu eroarea ''​ETIMEDOUT''​. Parametrul ''​abstime''​ este absolut și reprezintă numărul de secunde trecute de la 1 ianuarie 1970, ora 00:00. 
-==== Deblocarea unui singur fir blocat la o variabilă condiție ==== 
- 
-Pentru a debloca un singur fir de execuție blocat la o variabilă condiție se va semnaliza variabila condiție folosind [[http://​linux.die.net/​man/​3/​pthread_cond_signal|pthread_cond_signal]]:​ 
- 
-<code c> 
-int pthread_cond_signal(pthread_cond_t *cond); 
-</​code>​ 
- 
-Dacă la variabila condiție nu așteaptă niciun fir de execuție, apelul funcției nu are efect și semnalizarea se va **pierde**. Dacă la variabila condiție așteaptă mai multe fire de execuție, va fi deblocat doar unul dintre acestea. Alegerea firului care va fi deblocat este făcută de planificatorul de fire de execuție. Nu se poate presupune că firele care așteaptă vor fi deblocate în ordinea în care și-au început așteptarea. Firul de execuție apelant trebuie să dețină **mutex-ul** asociat variabilei condiție în momentul apelului acestei funcții. 
- 
-Exemplu: 
- 
-<code c> 
-pthread_mutex_t count_lock; 
-pthread_cond_t ​ count_nonzero;​ 
-unsigned ​       count; 
-  
-void decrement_count() { 
-    pthread_mutex_lock(&​count_lock);​ 
-    while (count == 0) 
-        pthread_cond_wait(&​count_nonzero,​ &​count_lock);​ 
-    count = count - 1; 
-    pthread_mutex_unlock(&​count_lock);​ 
-} 
-  
-void increment_count() { 
-    pthread_mutex_lock(&​count_lock);​ 
-    count = count + 1; 
-    pthread_cond_signal(&​count_nonzero);​ 
-    pthread_mutex_unlock(&​count_lock);​ 
-} 
-</​code>​ 
-==== Deblocarea tuturor firelor blocate la o variabilă condiție ==== 
- 
-Pentru a debloca toate firele de execuție blocate la o variabilă condiție, se semnalizează variabila condiție folosind [[http://​linux.die.net/​man/​3/​pthread_cond_broadcast|pthread_cond_broadcast]]:​ 
- 
-<code c> 
-int pthread_cond_broadcast(pthread_cond_t *cond); 
-</​code>​ 
- 
-Dacă la variabila condiție nu așteaptă niciun fir de execuție, apelul funcției nu are efect și semnalizarea se va **pierde**. Dacă la variabila condiție așteaptă fire de execuție, toate acestea vor fi deblocate, dar vor **concura** pentru ocuparea mutex-ului asociat variabilei condiție. Firul de execuție apelant trebuie să dețină mutex-ul asociat variabilei condiție în momentul apelului acestei funcții. 
-==== Exemplu de utilizare a variabilelor de condiție ==== 
- 
-În următorul program se utilizează o barieră pentru a sincroniza firele de execuție ale programului. Bariera este implementată cu ajutorului unei variabile de condiție. 
- 
-<code c> 
-#include <​stdio.h>​ 
-#include <​pthread.h>​ 
-  
-#define NUM_THREADS 5 
-  
-// implementarea unei bariere *non-reutilizabile* cu variabile de condiție 
-struct my_barrier_t { 
-    // mutex folosit pentru a serializa accesele la datele interne ale barierei 
-    pthread_mutex_t lock; 
-    ​ 
-    // variabila de condiție pe care se așteptă sosirea tuturor firelor de execuție 
-    pthread_cond_t ​ cond; 
-    ​ 
-    // număr de fire de execuție care trebuie să mai vină pentru a elibera bariera 
-    int nr_still_to_come;​ 
-}; 
-  
-struct my_barrier_t bar; 
-  
-void my_barrier_init(struct my_barrier_t *bar, int nr_still_to_come) { 
-    pthread_mutex_init(&​bar->​lock,​ NULL); 
-    pthread_cond_init(&​bar->​cond,​ NULL); 
-    ​ 
-    // câte fire de execuție sunt așteptate la barieră 
-    bar->​nr_still_to_come = nr_still_to_come;​ 
-} 
-  
-void my_barrier_destroy(struct my_barrier_t *bar) { 
-    pthread_cond_destroy(&​bar->​cond);​ 
-    pthread_mutex_destroy(&​bar->​lock);  ​ 
-} 
-  
-void *thread_routine(void *arg) { 
-    int thd_id = (int) arg; 
-  
-    // înainte de a lucra cu datele interne ale barierei trebuie să preluam mutex-ul 
-    pthread_mutex_lock(&​bar.lock);​ 
-  
-    printf("​thd %d: before the barrier\n",​ thd_id); 
-  
-    // suntem ultimul fir de execuție care a sosit la barieră? 
-    int is_last_to_arrive = (bar.nr_still_to_come == 1); 
-    // decrementăm numarul de fire de execuție așteptate la barieră 
-    bar.nr_still_to_come --; 
-  
-    // cât timp mai sunt fire de execuție care nu au ajuns la barieră, așteptăm. 
-    while (bar.nr_still_to_come != 0) 
-        // mutex-ul se eliberează automat înainte de a incepe așteptarea 
-        pthread_cond_wait(&​bar.cond,​ &​bar.lock); ​ 
-  
-    // ultimul fir de execuție ajuns la barieră va semnaliza celelalte fire  
-    if (is_last_to_arrive) { 
-        printf(" ​   let the flood in\n"​);​ 
-        pthread_cond_broadcast(&​bar.cond);​ 
-    } 
-  ​ 
-    printf("​thd %d: after the barrier\n",​ thd_id); 
-  
-    // la ieșirea din funcția de așteptare se preia automat mutex-ul, care trebuie eliberat 
-    pthread_mutex_unlock(&​bar.lock);​ 
-  ​ 
-    return NULL; 
-} 
-  
-int main(void) { 
-    int i; 
-    pthread_t tids[NUM_THREADS];​ 
-  
-    my_barrier_init(&​bar,​ NUM_THREADS);​ 
-  
-    for (i = 0; i < NUM_THREADS;​ i++) 
-        pthread_create(&​tids[i],​ NULL, thread_routine,​ (void *) i); 
-  
-    for (i = 0; i < NUM_THREADS;​ i++) 
-        pthread_join(tids[i],​ NULL); 
-  
-    my_barrier_destroy(&​bar);​ 
-    ​ 
-    return 0; 
-} 
-</​code>​ 
- 
-<code bash> 
-so@spook$ gcc -Wall cond_var.c -pthread 
-so@spook$ ./​a.out ​ 
-thd 0: before the barrier 
-thd 2: before the barrier 
-thd 3: before the barrier 
-thd 4: before the barrier 
-thd 1: before the barrier 
-    let the flood in 
-thd 1: after the barrier 
-thd 2: after the barrier 
-thd 3: after the barrier 
-thd 4: after the barrier 
-thd 0: after the barrier 
-</​code>​ 
- 
-Din execuția programului se observă: 
-  *ordinea în care sunt planificate firele de execuție **nu** este neapărat cea a creării lor 
-  *ordinea în care sunt trezite firele de execuție ce așteaptă la o variabilă de condiție **nu** este neapărat ordinea în care acestea au intrat în așteptare. ​ 
- 
-===== Barieră ===== 
- 
-Standardul POSIX definește și un set de funcții și structuri de date de lucru cu bariere. Aceste funcții sunt disponibile dacă se definește macro-ul ''​_XOPEN_SOURCE''​ la o valoare >= 600. 
- 
- 
-==== Inițializarea/​distrugerea unei bariere ==== 
- 
-Bariera se va inițializa folosind [[http://​linux.die.net/​man/​3/​pthread_barrier_init|pthread_barrier_init]] și se va distruge folosind [[http://​linux.die.net/​man/​3/​pthread_barrier_destroy|pthread_barrier_destroy]]. 
- 
-<code c> 
-// pentru a folosi funcțiile de lucru cu bariere e nevoie să se definească ​ 
-// _XOPEN_SOURCE la o valoare >= 600. Pentru detalii consultați feature_test_macros(7). 
-#define _XOPEN_SOURCE 600 
-#include <​pthread.h>​ 
-  
-// attr    -> un set de atribute, poate fi NULL (se folosesc atribute implicite) 
-// count   -> numărul de fire de execuție care trebuie să ajungă 
-//            la barieră pentru ca aceasta să fie eliberată 
-int pthread_barrier_init(pthread_barrier_t *barrier, ​ 
-                         const pthread_barrierattr_t *attr, ​ 
-                         ​unsigned count); 
-  
-// trebuie să nu existe fire de execuție în așteptare la barieră 
-// înainte de a apela funcția _destroy, altfel, se întoarce EBUSY  
-// și nu se distruge bariera. 
-int pthread_barrier_destroy(pthread_barrier_t *barrier); 
-</​code>​ 
-==== Așteptarea la o barieră ==== 
- 
-Așteptarea la barieră se face prin apelul [[http://​linux.die.net/​man/​3/​pthread_barrier_wait|pthread_barrier_wait]]:​ 
- 
-<code c> 
-#define _XOPEN_SOURCE 600 
-#include <​pthread.h>​ 
-int pthread_barrier_wait(pthread_barrier_t *barrier); 
-</​code>​ 
- 
-Dacă bariera a fost creată cu ''​count=N'',​ primele ''​N-1''​ fire de execuție care apelează ''​pthread_barrier_wait''​ se blochează. Când sosește **ultimul** (al ''​N''​-lea),​ va debloca toate cele ''​N-1''​ fire de execuție. Funcția ''​pthread_barrier_wait''​ întoarce trei valori: 
-  *''​EINVAL''​ – în cazul în care bariera nu este inițializată (singura eroare definită) 
-  *''​PTHREAD_BARRIER_SERIAL_THREAD''​ – în caz de succes, un singur fir de execuție va întoarce valoarea aceasta – nu e specificat care este acel fir de execuție (nu e obligatoriu să fie ultimul ajuns la barieră) 
-  *''​0''​ – valoare întoarsă în caz de succes de celelalte ''​N-1''​ fire de execuție. ​ 
-==== Exemplu de utilizare a barierei ==== 
- 
-Cu bariere POSIX, programul de mai sus poate fi simplificat:​ 
- 
-<code c> 
-#define _XOPEN_SOURCE 600 
-#include <​pthread.h>​ 
-#include <​stdio.h>​ 
- 
-#define NUM_THREADS 5 
-  
-pthread_barrier_t barrier; 
-  
-void *thread_routine(void *arg) { 
-    int thd_id = (int) arg; 
-    int rc; 
-  
-    printf("​thd %d: before the barrier\n",​ thd_id); 
-  
-    // toate firele de execuție așteaptă la barieră. 
-    rc = pthread_barrier_wait(&​barrier);​ 
-    if (rc == PTHREAD_BARRIER_SERIAL_THREAD) { 
-        // un singur fir de execuție (posibil ultimul) va întoarce PTHREAD_BARRIER_SERIAL_THREAD 
-        // restul firelor de execuție întorc 0 în caz de succes. 
-        printf(" ​  let the flood in\n"​); ​ 
-    } 
-    ​ 
-    printf("​thd %d: after the barrier\n",​ thd_id); 
-  ​ 
-    return NULL; 
-} 
-  
-int main(void) ​ 
-{ 
-    int i; 
-    pthread_t tids[NUM_THREADS];​ 
-  
-    // bariera este inițializată o singură dată și folosită de toate firele de execuție 
-    pthread_barrier_init(&​barrier,​ NULL, NUM_THREADS);​ 
-  
-    // firele de execuție vor executa codul funcției '​thread_routine'​. 
-    // în locul unui pointer la date utile, se trimite în ultimul argument 
-    // un întreg - identificatorul firului de execuție 
-    for (i = 0; i < NUM_THREADS;​ i++) 
-        pthread_create(&​tids[i],​ NULL, thread_routine,​ (void *) i); 
-  
-    // așteptăm ca toate firele de execuție să se termine 
-    for (i = 0; i < NUM_THREADS;​ i++) 
-        pthread_join(tids[i],​ NULL); 
-  
-    // eliberăm resursele barierei 
-    pthread_barrier_destroy(&​barrier);​ 
-  ​ 
-    return 0; 
-} 
-</​code>​ 
- 
-<code bash> 
-so@spook$ gcc -Wall barrier.c -lpthread 
-so@spook$ ./​a.out ​ 
-thd 0: before the barrier 
-thd 2: before the barrier 
-thd 1: before the barrier 
-thd 3: before the barrier 
-thd 4: before the barrier 
-   let the flood in 
-thd 4: after the barrier 
-thd 2: after the barrier 
-thd 3: after the barrier 
-thd 0: after the barrier 
-thd 1: after the barrier 
-</​code>​ 
- 
- 
-====== Exerciţii de laborator ====== 
- 
-===== Exercițiul 0 - Joc interactiv (2p) ===== 
- 
-  * Detalii desfășurare [[http://​ocw.cs.pub.ro/​courses/​so/​meta/​notare#​joc_interactiv|joc]]. 
- 
-===== Linux (9p) ===== 
- 
-Pentru rezolvarea laboratorului,​ va rugam sa clonati [[https://​www.github.com/​upb-fils/​sde|repository-ul]]. daca il aveti deja, va rugam sa rulati ''​git pull''​. ​ 
- 
-<note tip> Pentru a vă ajuta la implementarea exercițiilor din laborator, în directorul ''​utils''​ din arhivă există un fișier ''​utils.h''​ cu funcții utile. </​note>​ 
- 
-<​note>​Pentru a instala paginile de manual pentru '​pthreads'​ 
-<code bash>​sudo apt-get install manpages-posix manpages-posix-dev</​code>​ 
-</​note>​ 
- 
-==== Exercițiul 1 - Thread Stack (2p) ==== 
- 
-Intrați în directorul ''​1-th_stack''​ și inspectați sursa, apoi compilați și rulați programul. Urmăriți cu ''​pmap''​ sau folosind ''​procfs''​ cum se modifică spațiul de adresă al programului:​ 
-<code bash> 
-watch -d pmap $(pidof th_stack) 
-watch -d cat /​proc/​$(pidof th_stack)/​maps 
-</​code> ​ 
- 
-Zonele de memorie cu dimensiunea de 8MB (8192KB) care se creează după fiecare apel ''​pthread_create''​ reprezintă noile //stive// alocate de către biblioteca ''​libpthread''​ pentru fiecare thread în parte. Observați că, în plus, se mai mapează de fiecare dată o pagină (4KB) cu protecția ''​%%---p%%''​ (PROT_NONE, private - vizibil în ''​procfs''​) care are rolul de %%"​%%pagină de gardă%%"​%%. ​ 
- 
-Motivul pentru care nu se termină programul este prezența unui ''​​while(1)''​​ în funcția thread-urilor. Folosiți ''​Ctrl+C''​ pentru a termina programul. ​ 
-==== Exercițiul 2 - Fire de execuție vs Procese (2p) ==== 
- 
-Intrați în directorul ''​2-th_vs_proc''​ și inspectați sursele. Ambele programe simulează un server care creează fire de execuție/​procese. Compilați și rulați pe rând ambele programe. ​ 
- 
-În timp ce rulează, afișați, într-o altă consolă, câte fire de execuție/​procese sunt create în ambele situații folosind comanda ''​ps -L -C <​nume_program>''​. 
- 
-<code bash> 
-ps -L -C threads 
-ps -L -C processes 
-</​code>​ 
- 
-Verificați ce se întâmplă dacă la un moment dat un fir de execuție ​ moare (sau un proces, în funcție de ce executabil testați). Testați utilizând funcția ''​do_bad_task''​ la fiecare al 4-lea fir de execuție/​process. 
-==== Exercițiul 3 - Thread safety (2p) ==== 
- 
-<note important>​ 
-Datorită faptului că mașina virtuală ''​spook''​ are un singur core virtual, exercițiul următor trebuie realizat pe mașina fizică pentru a permite mai multor thread-uri să ruleze în același moment de timp. 
-</​note>​ 
- 
-Intrați în directorul ''​3-safety''​ și inspectați sursa ''​malloc.c''​. Funcțiile ''​thread_function''​ și ''​main''​ **NU** sunt thread-safe relativ la variabilele ''​global_storage''​ și ''​function_global_storage''​ (revedeți semnificația lui ''​[[http://​en.wikipedia.org/​wiki/​Thread_safety | thread safety]]''​). Există o [[https://​en.wikipedia.org/​wiki/​Race_condition | condiție de cursă]] între cele două thread-uri create la incrementarea variabilei ''​function_global_storage'',​ declarată în funcția ''​thread_function'',​ și o altă condiție de cursă între toate thread-urile procesului la incrementarea variabilei globale ''​global_storage''​. 
-<note tip> 
-Un utilitar foarte folositor este ''​helgrind'',​ care poate detecta automat aceste condiții de cursă. Îl putem folosi în cazul nostru așa: 
-<code bash> 
-valgrind --tool=helgrind ./mutex 
-</​code>​ 
-</​note>​ 
-* ne vom concentra pentru rezolvarea TODO1 pe executabilui mutex 
- 
-''​TODO 1'':​ Pentru a rezolva aceste doua conditii de cursa apelati functia increase_numbers intr-un mod thread_safe cu ajutor API-ului pus la dispozitie de critical.h 
- 
-În fișierul ''​malloc.c''​ se creează ''​NUM_THREADS''​ thread-uri care alocă memorie în 1000 runde. Sunt șanse mari ca thread-urile să execute apeluri ''​malloc''​ concurente. ​ 
-După ce a-ti rezolvat ''​TODO1'',​ compilati și rulati de mai multe ori. Observăm că programul rulează cu succes. Pentru a face verificări suplimentare,​ rulăm din nou ''​helgrind'':​ 
- 
-<code bash> 
-valgrind --tool=helgrind ./mutex 
-</​code>​ 
- 
-Observăm că nici ''​helgrind''​ nu raportează vreo eroare, lucru care conduce la faptul că funcția ''​malloc''​ ar fi thread-safe. (chiar daca acesta nu este protejat de API-ul pus la dispozitie) ​ 
-Pentru a putea fi siguri trebuie să consultăm paginile de manual și codul sursă. 
- 
-<note important>​ 
-Este important de știut că anumite funcții sunt thread-safe iar altele nu. Găsiți o listă cu funcțiile care nu sunt thread-safe în pagina de manual [[http://​man7.org/​linux/​man-pages/​man7/​pthreads.7.html|pthreads(7)]],​ în secțiunea ''​Thread-safe functions''​. 
- 
-Funcția ''​malloc''​ din implementarea GLIBC **este thread-safe**,​ lucru indicat în pagina de manual [[http://​man7.org/​linux/​man-pages/​man3/​malloc.3.html#​NOTES| malloc(3)]] (al treilea paragraf din secțiunea ''​NOTES''​) și vizibil în codul sursă prin prezența câmpului ''​mutex''​ în [[https://​sourceware.org/​git/?​p=glibc.git;​a=blob;​f=malloc/​malloc.c;​h=f361bad636167cf1680cb75b5098232c9232d771;​hb=HEAD#​l1672|structura malloc_state]]. 
-</​note>​ 
- 
-''​TODO 2'':​ Implementati un spinlock folosindu-va de operatii atomice. 
-Operatiile atomice existente in standardul GCC le gasiti la [[https://​gcc.gnu.org/​onlinedocs/​gcc/​_005f_005fatomic-Builtins.html | __atomic functions]] 
- 
-In fisierul critical.c trebuie sa completati in dreptul comentarilor asociate ''​TODO 2'',​ avand la dispozitie hint-uri. 
- 
-Testati si rulati de mai multe ori, pentru a verifica consistenta variabilei globale: global_storage,​ executabilul ''​./​spin''​. 
- 
-==== Exercițiul 4 - Blocked (2p) ==== 
- 
- 
-Inspectați fișierul ''​blocked.c''​ din directorul ''​4-blocked'',​ compilați și executați binarul (repetați până detectați blocarea programului). Programul creează două fire de execuție care caută un număr magic, fiecare în intervalul propriu (nu este neapărat necesar ca numărul să fie găsit). Fiecare fir de execuție, pentru fiecare valoare din intervalul propriu, verifică dacă este valoarea căutată: 
-  * dacă da, marchează un câmp ''​found''​ pentru a înștiința și celălalt fir de execuție că a găsit numărul căutat. 
-  * dacă nu, inspectează câmpul ''​found''​ al structurii celuilalt fir de execuție, pentru a vedea dacă acesta a găsit deja numărul căutat. ​ 
- 
-Determinați cauza blocării, reparați programul și explicați soluția. Puteți utiliza ''​helgrind'',​ unul din tool-urile ''​valgrind'',​ pentru a detecta problema: <code bash> 
-$ valgrind --tool=helgrind ./blocked 
-</​code>​ 
- 
-<note tip> 
-Așa cum ne arată și ''​helgrind'',​ problema constă în faptul că cele două thread-uri iau cele două mutex-uri în ordinea inversă, situație foarte probabilă în a cauza un [[https://​en.wikipedia.org/​wiki/​Deadlock|deadlock]]. 
-</​note>​ 
-==== Exercițiul 5 - Implementare comportament pthread_once (1p)==== 
- 
-Aveți o funcție de inițializare pe care vreți să o apelați o singură dată. Pornind de la sursa ''​once.c''​ din directorul ''​5-once'',​ asigurați-vă că funcția ''​init_func''​ este apelată o singură dată. **Nu** aveți voie să modificați funcția ''​init_func''​ sau să folosiţi ''​pthread_once''​. 
- 
-Citiți despre funcționalitatea [[http://​linux.die.net/​man/​3/​pthread_once | pthread_once]] și revedeți secțiunea despre [[#mutex | mutex]]. 
- 
-==== Exercitiul 6 - Mutex vs Spinlock (1p) ==== 
- 
-Dorim să testăm care varianta este mai eficientă pentru a proteja incrementarea unei variabile. 
- 
-Intrați în directorul ''​6-spin'',​ inspectați și compilați sursa ''​spin.c''​. În urma compilării vor rezulta două executabile,​ unul care folosește un mutex pentru sincronizare,​ iar altul un spinlock. 
- 
-Comparați timpii de execuție: 
-<code bash> 
-time ./mutex 
-time ./spin 
-</​code>​ 
- 
-<note tip> 
-Atunci când un fir de execuție găsește mutex-ul ocupat se va bloca. Atunci când un fir de execuție găsește spinlock-ul ocupat va face busy-waiting. 
-</​note>​ 
- 
-/* 
-==== Extra ==== 
-  - Descărcați [[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab08-extra.zip|arhiva]] cu scripturi Python 
-    * Rulați tls.py; ce observați? De ce cu TLS programul afișează rezultatul corect, iar cu o variabilă globală nu? 
-    * Rulați profile_threads.py și extindeți scriptul pentru a intercepta trace-ului firelor de execuție. Vezi funcția [[http://​docs.python.org/​library/​threading.html|settrace]] 
-    * Analizați cu [[http://​linux.die.net/​man/​1/​ltrace|ltrace]] apelurile pthread_* le care le face Python când creați un fir de execuție; ce observați? 
- 
-*/ 
-===== Resurse utile ===== 
- 
-[[http://​www.yolinux.com/​TUTORIALS/​LinuxTutorialPosixThreads.html | LinuxTutorialPosixThreads ]] 
- 
-[[https://​computing.llnl.gov/​tutorials/​pthreads/​ | POSIX Threads Programming]] 
sde/laboratoare/09_ro_python.1586861837.txt.gz · Last modified: 2020/04/14 13:57 by ioana_maria.culic
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0