This shows you the differences between two versions of the page.
sde:laboratoare:09_ro_python [2020/04/15 10:13] ioana_maria.culic [Exercițiul 4 - Maximul] |
— (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): | ||
- | threads[i].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=2) | ||
- | |||
- | add_counter = 0 | ||
- | subtract_counter = 0 | ||
- | |||
- | def add_routine(): | ||
- | global add_counter | ||
- | | ||
- | thread_id = threading.get_ident() | ||
- | # acquire global mutex | ||
- | my_sem.acquire() | ||
- | |||
- | # print and modify global_counter | ||
- | print("Thread {} says add_counter={}".format (thread_id, add_counter)) | ||
- | add_counter = add_counter + 1 | ||
- | |||
- | # release mutex - now other threads can modify global_counter | ||
- | my_sem.release() | ||
- | |||
- | def subtract_routine(): | ||
- | global subtract_counter | ||
- | | ||
- | thread_id = threading.get_ident() | ||
- | # acquire global mutex | ||
- | my_sem.acquire() | ||
- | |||
- | # print and modify global_counter | ||
- | print("Thread {} says subtract_counter={}".format (thread_id, subtract_counter)) | ||
- | subtract_counter = subtract_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): | ||
- | t1 = threading.Thread (target=add_routine) | ||
- | t2 = threading.Thread (target=subtract_routine) | ||
- | threads.append (t1) | ||
- | t1.start() | ||
- | threads.append (t2) | ||
- | t2.start() | ||
- | |||
- | for i in range (NUM_THREADS): | ||
- | threads[i].join() | ||
- | </code> | ||
- | |||
- | <code bash> | ||
- | $ python3 example.py | ||
- | Thread 123145375227904 says add_counter=0 | ||
- | Thread 123145380483072 says subtract_counter=0 | ||
- | Thread 123145375227904 says add_counter=1 | ||
- | Thread 123145375227904 says subtract_counter=-1 | ||
- | Thread 123145375227904 says add_counter=2 | ||
- | Thread 123145380483072 says subtract_counter=-2 | ||
- | Thread 123145375227904 says subtract_counter=-2 | ||
- | Thread 123145385738240 says add_counter=3 | ||
- | Thread 123145380483072 says add_counter=4 | ||
- | Thread 123145375227904 says subtract_counter=-4 | ||
- | </code> | ||
- | <note info> | ||
- | Putem observa că cele două thread-uri (add si subtract) sunt rulate în paralel. | ||
- | </note> | ||
- | ===== 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). | ||
- | |||
- | În Python, o variabilă de condiție este reprezentată de clasa [[https://docs.python.org/3/library/threading.html#threading.Condition|Condition]]. | ||
- | |||
- | 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. | ||
- | |||
- | La crearea condiției, putem pasa constructorului ca parametru un mutex sau putem lăsa constructorul să creeze unul. | ||
- | <code python> | ||
- | import threading | ||
- | |||
- | mutex = threading.Lock() | ||
- | mutex_cond = threading.Condition (mutex) | ||
- | default_mutex_cond = threading.Condition() | ||
- | </code> | ||
- | |||
- | 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. | ||
- | |||
- | <note info> | ||
- | Vom folosit funcțiile **acquire** și **release** pe obiectul condiție. | ||
- | </note> | ||
- | ==== 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 una din funcțiile [[https://docs.python.org/3/library/threading.html#threading.Condition.wait|wait]] sau [[https://docs.python.org/3/library/threading.html#threading.Condition.wait_for|wait_for]]: | ||
- | |||
- | <code python> | ||
- | wait(timeout=None) | ||
- | wait_for(predicate, timeout=None) | ||
- | </code> | ||
- | |||
- | Diferența între cele două funcții este că **wait** așteaptă până o notificare este emisă, în timp ce **wait_for** așteaptă până predicatul va avea valoarea **True**. | ||
- | |||
- | Firul de execuție apelant trebuie să fi **ocupat** deja mutex-ul asociat, în momentul apelului. Funcția ''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 ''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 ''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. | ||
- | |||
- | |||
- | ==== 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 [[https://docs.python.org/3/library/threading.html#threading.Condition.notify|notify]]: | ||
- | |||
- | <code python> | ||
- | notify(n=1) | ||
- | </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 python> | ||
- | import threading | ||
- | import time | ||
- | |||
- | mutex = threading.Lock() | ||
- | cond = threading.Condition(mutex) | ||
- | |||
- | count = 0 | ||
- | |||
- | def decrement_count(): | ||
- | global count | ||
- | id = threading.get_ident() | ||
- | cond.acquire() | ||
- | print ('{} a blocat mutex-ul.'.format (id)) | ||
- | while count == 0: | ||
- | cond.wait() | ||
- | count = count - 1 | ||
- | print ('count = {}' .format(count)) | ||
- | cond.release() | ||
- | print ('{} a eliberat mutex-ul.'.format(id)) | ||
- | |||
- | |||
- | def increment_count(): | ||
- | global count | ||
- | id = threading.get_ident() | ||
- | if cond.acquire(blocking=False): | ||
- | print ('Mutex-ul a fost eliberat') | ||
- | print ('{} a blocat mutex-ul.'.format(id)) | ||
- | count = count + 1 | ||
- | cond.notify() | ||
- | print ('count = {}' .format(count)) | ||
- | cond.release() | ||
- | print ('{} a eliberat mutex-ul.'.format(id)) | ||
- | |||
- | t1 = threading.Thread (target=decrement_count) | ||
- | t1.start() | ||
- | |||
- | time.sleep (2) | ||
- | |||
- | t2 = threading.Thread (target=increment_count) | ||
- | t2.start() | ||
- | </code> | ||
- | <note info> | ||
- | Putem observa că la apelul **wait**, mutex-ul este eliberat si poate fi ocupat de un alt thread. | ||
- | </note> | ||
- | ==== 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 [[https://docs.python.org/3/library/threading.html#threading.Condition.notify_all|notify_all]]: | ||
- | |||
- | <code python> | ||
- | notify_all() | ||
- | </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 python> | ||
- | import threading | ||
- | |||
- | NUM_THREADS = 5 | ||
- | nr_still_to_come = NUM_THREADS | ||
- | |||
- | condition = threading.Condition() | ||
- | |||
- | def thread_routine(): | ||
- | global nr_still_to_come | ||
- | id = threading.get_ident() | ||
- | |||
- | # înainte de a lucra cu datele interne ale barierei trebuie să preluam mutex-ul | ||
- | condition.acquire() | ||
- | |||
- | print("Thread {}: before the barrier".format (id)) | ||
- | |||
- | # suntem ultimul fir de execuție care a sosit la barieră? | ||
- | if nr_still_to_come == 1: | ||
- | is_last_to_arrive = True | ||
- | else: | ||
- | is_last_to_arrive = False | ||
- | # decrementăm numarul de fire de execuție așteptate la barieră | ||
- | nr_still_to_come = nr_still_to_come - 1 | ||
- | |||
- | # cât timp mai sunt fire de execuție care nu au ajuns la barieră, așteptăm. | ||
- | while nr_still_to_come != 0: | ||
- | # mutex-ul se eliberează automat înainte de a incepe așteptarea | ||
- | condition.wait() | ||
- | |||
- | # ultimul fir de execuție ajuns la barieră va semnaliza celelalte fire | ||
- | if is_last_to_arrive: | ||
- | print(" let the flood in") | ||
- | condition.notify_all() | ||
- | |||
- | print("Thread {}: after the barrier". format (id)) | ||
- | |||
- | # la ieșirea din funcția de așteptare se preia automat mutex-ul, care trebuie eliberat | ||
- | condition.release() | ||
- | |||
- | threads = [] | ||
- | |||
- | for i in range (NUM_THREADS): | ||
- | t = threading.Thread (target=thread_routine) | ||
- | threads.append (t) | ||
- | t.start() | ||
- | |||
- | for i in range (NUM_THREADS): | ||
- | threads[i].join() | ||
- | </code> | ||
- | |||
- | <code bash> | ||
- | $ python3 example.py | ||
- | Thread 123145396199424: before the barrier | ||
- | Thread 123145401454592: before the barrier | ||
- | Thread 123145406709760: before the barrier | ||
- | Thread 123145411964928: before the barrier | ||
- | Thread 123145417220096: before the barrier | ||
- | let the flood in | ||
- | Thread 123145417220096: after the barrier | ||
- | Thread 123145411964928: after the barrier | ||
- | Thread 123145396199424: after the barrier | ||
- | Thread 123145401454592: after the barrier | ||
- | Thread 123145406709760: 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. | ||
- | |||
- | În Python, bariera este implementată prin clasa [[https://docs.python.org/3/library/threading.html#threading.Barrier|Barrier]]. | ||
- | |||
- | |||
- | ==== Crearea unei bariere ==== | ||
- | |||
- | La crearea unei bariere, se va specifica numărul de fire de execuție care vor **aștepta** la barieră: | ||
- | <code python> | ||
- | barrier = threading.Barrier (parties, action=None, timeout=None) | ||
- | </code> | ||
- | |||
- | Action este o funcție care va fi apelată doar de primul thread care a fost executat **după** barieră. | ||
- | |||
- | ==== Așteptarea la o barieră ==== | ||
- | |||
- | Așteptarea la barieră se face prin apelul [[https://docs.python.org/3/library/threading.html#threading.Barrier.wait|wait]]: | ||
- | |||
- | <code python> | ||
- | barrier.wait() | ||
- | </code> | ||
- | |||
- | Dacă bariera a fost creată cu ''parties=NUM_THREADS'', primele ''NUM_THREADS-1'' fire de execuție care apelează ''wait'' se blochează. Când sosește **ultimul** (al ''NUM_THREAD''-lea), va debloca toate cele ''NUM_THREAD-1'' fire de execuție. Funcția ''wait'' întoarce valori de la 0 la NUM_THREADS-1, câte o valoare diferită pentru fiecare thread. | ||
- | |||
- | ==== Exemplu de utilizare a barierei ==== | ||
- | |||
- | Cu bariere, programul de mai sus poate fi simplificat: | ||
- | |||
- | <code python> | ||
- | import threading | ||
- | |||
- | NUM_THREADS = 5 | ||
- | |||
- | def print_flood(): | ||
- | print(" let the flood in") | ||
- | |||
- | def thread_routine(): | ||
- | id = threading.get_ident() | ||
- | |||
- | print("Thread {}: before the barrier".format(id)) | ||
- | |||
- | # toate firele de execuție așteaptă la barieră. | ||
- | rc = barrier.wait() | ||
- | print("Thread {}: after the barrier".format(id)) | ||
- | |||
- | # bariera este inițializată o singură dată și folosită de toate firele de execuție | ||
- | # cel de-al doilea parametru este o functie care va fi apelata doar de primul thread care a primit release | ||
- | barrier = threading.Barrier (NUM_THREADS, print_flood) | ||
- | |||
- | threads = [] | ||
- | for i in range (NUM_THREADS): | ||
- | t = threading.Thread (target=thread_routine) | ||
- | threads.append (t) | ||
- | t.start() | ||
- | for i in range (NUM_THREADS): | ||
- | threads[i].join() | ||
- | </code> | ||
- | |||
- | <code bash> | ||
- | $ python3 example.py | ||
- | Thread 123145501237248: before the barrier | ||
- | Thread 123145506492416: before the barrier | ||
- | Thread 123145511747584: before the barrier | ||
- | Thread 123145517002752: before the barrier | ||
- | Thread 123145522257920: before the barrier | ||
- | let the flood in | ||
- | Thread 123145522257920: after the barrier | ||
- | Thread 123145501237248: after the barrier | ||
- | Thread 123145506492416: after the barrier | ||
- | Thread 123145511747584: after the barrier | ||
- | Thread 123145517002752: after the barrier | ||
- | </code> | ||
- | |||
- | |||
- | ====== Exerciţii de laborator ====== | ||
- | |||
- | În rezolvarea laboratorului folosiți repository-ul de github. Pentru a descărca repository-ul, rulati comanda git clone https://github.com/UPB-FILS/sde.git in terminal. | ||
- | |||
- | ===== Exercițiul 1 - Print once ===== | ||
- | În fișierul main.py din directorul 1-once am creat un program care folosește 10 thread-uri pentru a afișa un text de 10 ori. Modificați funcția **print_text** astfel încât în urma rulării programului, textul să fie afișat o singură dată pe ecran. | ||
- | |||
- | ===== Exercițiul 2 - Magic number ===== | ||
- | |||
- | În fișierul main.py din directorul 2-magic am descris o funcție care caută primul palindrom în intervalul min-max, unde min și max sunt două valori pe care funcția le primește ca parametrii, folosind două thread-uri. | ||
- | |||
- | Modificați codul astfel încât variabila globală **magic_number** să rețină prima valoare găsită (dacă un thread a găsit o valoare, cel de-al doilea nu va mai stoca valoarea găsită). | ||
- | |||
- | |||
- | ===== Exercițiul 3 - Media artimetică ===== | ||
- | |||
- | |||
- | În fișierul main.py din directorul 3-avg folosim două thread-uri pentru a calcula media aritmetica a unor numere generate aleator. Folosiți variabila **condition** pentru a asigura funcționarea corectă a programului. | ||
- | |||
- | |||
- | ===== Exercițiul 4 - Sleep ===== | ||
- | |||
- | Creați un program care lansează 10 thread-uri care fac sleep pentru un număr aleator de secunde, transmis de la programul principal. După ce toate thread-urile au facut sleep, afișați un mesaj în consolă. Folosiți o structură de tip barieră pentru a asigura funcționarea corectă a programului. |