This is an old revision of the document!
Pentru parcurgerea demo-urilor, folosim arhiva aferentă. Demo-urile rulează pe Linux. Descărcăm arhiva folosind comanda
wget http://elf.cs.pub.ro/so/res/cursuri/curs-09-demo.zip
și apoi decomprimăm arhiva
unzip curs-09-demo.zip
și accesăm directorul rezultat în urma decomprimării
cd curs-09-demo/
Acum putem parcurge secțiunile cu demo-uri de mai jos.
Dorim să urmărim ce se întâmplă în cazul în care avem date partajate într-un mediu multithread. Pentru aceasta accesăm subdirectorul list-excl/
; urmărim conținutul fișierelor thread-list-app.c
și list.c
. Este vorba de o aplicație care lucrează cu liste înlănțuite într-un mediu multithreaded. Vom observa că există riscul ca datele să fie corupte, fiind necesară sincronizare.
Compilăm fișierele folosind make
. Rezultă două fișiere în format executabil: thread-list-app
și thread-list-app-mutex
.
Programul thread-list-app-mutex
folosește intern un mutex pentru sincronizare. Pentru aceasta folosim macro-ul USE_MUTEX
pe care îl definim în fișierul Makefile
.
Ca să urmărim ce se întâmplă cu o aplicație multithreaded cu date partajate, rulăm de mai multe ori programul thread-list-app
, până la obținerea unei erori:
./thread-list-app
thread-list-app.c
macro-urile NUM_THREADS
și NUM_ROUNDS
la alte valori (probabil mai mari). Apoi recompilăm folosind make
și rulăm din nou programul thread-list-app
.
Eroare este cauzată de coruperea pointerilor din lista înlănțuită a programului. Datele sunt accesate concurent, iar în absența sincronizării, vor fi corupte.
Soluția este să folosim un mutex pentru protejarea accesului la listă, lucru realizat în programul thread-list-app-mutex
. Dacă vom rula de mai multe ori programul thread-list-app-mutex
, nu vom obține niciodată eroare. Avem acces exclusiv la datele partajate deci am rezolvat problema coruperii datelor din cauza accesului concurent neprotejat.
Dorim să investigăm overheadul produs când folosim spinlock-uri și mutex-uri pentru acces exclusiv. Pentru aceasta accesăm subdirectorul spinlock-mutex/
; urmărim conținutul fișierului spinlock-mutex.c
. Acest fișiere folosește spinlock-uri sau mutex-uri pentru asigurarea accesului exclusiv.
Compilăm cele două programe folosind make
. Rezultă două fișiere în format executabil: spinlock
și mutex
.
spinlock-mutex/
.spin.c
.USE_SPINLOCK
.Makefile
.USE_SPINLOCK
.make
pentru a obține două executabile: spin
și mutex
.time
pentru a contabiliza timpul de rulare:time ./spin time ./mutex
./spin
nu petrece foarte mult în kernel space (system time – sys
în output-ul time
)?
Dorim să urmărim cum se manifestă o condiție de cursă de tipul TOCTTOU (time of check to time of use). Pentru aceasta accesăm subdirectorul tocttou/
; urmărim conținutul fișierului tocttou.c
. Fișierul simulează o problemă producător consumator în care producătorul produce câte NUM_ITEMS_PRODUCER
elemente, iar consumatorul consumă câte NUM_ITEMS_CONSUMER
elemente; dacă un consumator nu poate consuma elemente, își încheie execuția.
Compilăm programul folosind make
. Rezultă fișierul tocttou
în format executabil.
Dorim să urmărim cum se manifestă un deadlock. Pentru aceasta accesăm subdirectorul deadlock/
; urmărim conținutul fișierului deadlock.c
. Fișierul are o implementare didactică pentru două tipuri de thread-uri care obțin în ordine diferită două mutex-uri.
Compilăm programul folosind make
. Rezultă fișierul deadlock
în format executabil.
Dorim să urmărim cum se manifestă o problemă de așteptare nedefinită (indefinite wait). Pentru aceasta accesăm subdirectorul indefinite-wait/
; urmărim conținutul fișierului indefinite-wait.c
. Fișierul are o implementare nefucțională pentru problema producători-consumatori, lucru care conduce la așteptare nedefinită.
Compilăm programul folosind make
. Rezultă fișierul indefinite-wait
în format executabil.
Dorim să urmărim cum se comportă un program atunci când diferă dimensiunea regiunii critice folosită pentru acces exclusiv. Pentru aceasta accesăm subdirectorul granularity/
; urmărim conținutul fișierului granularity.c
. Fișierul are o implementare didactică în care mai multe thread-uri accesează o regiune critică.
Compilăm programul folosind make
. Rezultă două fișiere în format executabil: granuarity-fine
și granularity-coarse
. Fișierului executabil granularity-fine
îi corespunde o regiune critică de mică dimensiune, dar accesată des; fișierului executabil granularity-coarse
îi corespunde o regiune critică de dimensiune mai mare, dar accesată rar.
În cadrul codului, folosirea unei granularități fine sau nu este indicată de macro-ul GRANULARITY_TYPE
inițializat în Makefile.
Pentru a vedea impactul tipului de granularitate, rulăm cele două fișiere în format executabil și măsurăm timpul de rulare:
/usr/bin/time ./granularity-fine /usr/bin/time ./granularity-coarse
Observăm că dureaza semnificativ mai mult rularea executabilului cu granularitate fină. Acest lucru se întâmplă pentru că regiunea critică pe care acesta o protejează este mic, iar cea mai mare parte din timp o va consuma în operațiile cu mutex-ul (lock contention). Observăm acest lucru și din numărul mare de schimbări de context (voluntare sau nevoluntare).