Differences

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

Link to this comparison view

so:cursuri:curs-08 [2013/04/09 17:10]
razvan.deaconescu
so:cursuri:curs-08 [2019/04/10 23:07] (current)
razvan.deaconescu [Utilitate apeluri reentrante]
Line 1: Line 1:
 ====== Curs 08 - Fire de execuție ====== ====== Curs 08 - Fire de execuție ======
- 
-<​html>​ 
-<iframe src="​http://​prezi.com/​embed/​3noq9rp6p95k/?​bgcolor=ffffff&​amp;​lock_to_path=0&​amp;​autoplay=0&​amp;​autohide_ctrls=0&​amp;​features=undefined&​amp;​disabled_features=undefined"​ width="​550"​ height="​400"​ frameBorder="​0"></​iframe>​ 
-</​html>​ 
  
   * [[http://​prezi.com/​3noq9rp6p95k/​curs-8-so/?​kw=view-3noq9rp6p95k&​rc=ref-31844697 | Curs 08 - Fire de execuție (vizualizare Prezi)]]   * [[http://​prezi.com/​3noq9rp6p95k/​curs-8-so/?​kw=view-3noq9rp6p95k&​rc=ref-31844697 | Curs 08 - Fire de execuție (vizualizare Prezi)]]
   * [[http://​elf.cs.pub.ro/​so/​res/​cursuri/​SO_Curs-08.pdf | Curs 08 - Fire de execuție (PDF)]]   * [[http://​elf.cs.pub.ro/​so/​res/​cursuri/​SO_Curs-08.pdf | Curs 08 - Fire de execuție (PDF)]]
 +
 +  * [[https://​drive.google.com/​open?​id=1oXIYiGsEYUNviw2BJ27XFzJnIDcKg7H_MxcY0ifnMJA|Notițe de curs]]
  
   * Suport curs   * Suport curs
-    * Operating Systems Concepts+    * Operating Systems Concepts ​Essentials
       * Capitolul 4 - Threads       * Capitolul 4 - Threads
     * Modern Operating Systems     * Modern Operating Systems
Line 19: Line 17:
       * Capitolul 7 - Threads and Scheduling       * Capitolul 7 - Threads and Scheduling
       * Capitolul 8 - Thread Synchronization       * Capitolul 8 - Thread Synchronization
 +
 +<​html>​
 +  <​center>​
 +    <iframe src="​https://​prezi.com/​embed/​3noq9rp6p95k/?​bgcolor=ffffff&​amp;​lock_to_path=0&​amp;​autoplay=0&​amp;​autohide_ctrls=0&​amp;​features=undefined&​amp;​disabled_features=undefined"​ width="​550"​ height="​400"​ frameBorder="​0"></​iframe>​
 +  </​center>​
 +</​html>​
  
 ===== Demo-uri ===== ===== Demo-uri =====
  
-Pentru parcurgerea demo-urilor, ​folosiți ​[[http://​elf.cs.pub.ro/​so/​res/​cursuri/​curs-08.zip|arhiva aferentă]].+Pentru parcurgerea demo-urilor, ​folosim ​[[http://​elf.cs.pub.ro/​so/​res/​cursuri/​curs-08-demo.zip|arhiva aferentă]]. Demo-urile rulează pe Linux. Descărcăm arhiva folosind comanda<​code bash> 
 +wget http://​elf.cs.pub.ro/​so/​res/​cursuri/​curs-08-demo.zip 
 +</​code>​ și apoi decomprimăm arhiva<​code bash> 
 +unzip curs-08-demo.zip 
 +</​code>​ și accesăm directorul rezultat în urma decomprimării<​code bash> 
 +cd curs-08-demo/​ 
 +</​code>​ 
 + 
 +Acum putem parcurge secțiunile cu demo-uri de mai jos. 
 + 
 + 
 +==== Timp de creare procese și thread-uri ==== 
 + 
 +Dorim să investigăm timpul de creare a proceselor și thread-urilor;​ timpul de creare al proceselor va fi mai mare, ne interesează cu cât. Pentru aceasta accesăm subdirectorul ''​creation-time/'';​ urmărim conținutul fișierelor ''​process-overhead.c''​ și ''​thread-overhead.c''​. În aceste fișiere se creează, în 100 de runde, câte 100 de procese, respectiv thread-uri. Vor fi create, respectiv, 100 de procese și 100 de thread-uri. 
 + 
 +Compilăm cele două programe folosind ''​make''​. Rezultă două fișiere în format executabil: ''​process-overhead''​ și ''​thread-overhead''​.
  
-  - Overhead proces/​thread +Pentru a măsura timpul de creare vom rula cele două executabile sub comanda ​''/​usr/​bin/​time'':<​code bash>
-    * Deschideți directorul ​''​0-overhead/''​+
-    * Consultați fișierele ''​process-overhead.c''​ și ''​thread-overhead.c''​. +
-    * Folosiți comanda ''​make''​ pentru a obține executabilele ''​process-overhead''​ și ''​thread-overhead''​. +
-    * Rulați cele două executabile și contorizați statistici de rulare:<code bash>+
 /​usr/​bin/​time -v ./​process-overhead ​ > /dev/null /​usr/​bin/​time -v ./​process-overhead ​ > /dev/null
 /​usr/​bin/​time -v ./​thread-overhead ​ > /dev/null /​usr/​bin/​time -v ./​thread-overhead ​ > /dev/null
 </​code>​ </​code>​
-      * Urmăriți diferențele ​din ieșirea ​celor două comenzi. +Acum urmărim diferențele ​dintre output-ul ​celor două comenzi. 
-        * Urmăriți dimensiunea maximă a memoriei rezidente, numărul de page fault-uri, numărul de schimbări de context+ 
-        * Explicați diferențele ​între ​ieșiri. +Observăm că timpul de creare al proceselor este 2 până la 3 ori mai mare decât în cazul thread-urilor. Și este încă foarte mic ținând cont de faptul că am creat 10000 de procese: 100 runde a câte 100 de procese. Deștimpul de creare a thread-urilor este mai mic, în termeni absoluți ambii timpi sunt neglijabili. Desigur, contează faptul că se realizează doar ''​fork'',​ fără ''​exec''​. Mecanismul de copy-on-write aferent ''​fork''​ reduce semnificativ timpul de creare. 
-  ​Partajarea informației între ​thread-uri + 
-    * Deschideți directorul ​''​1-process_thread/''​+Observăm, de asemenea, că dimensiunea maximă a spațiului de memorie alocată (//Maximum resident set size//) este mai mare în cazul thread-urilor. Un thread nou creat ocupă spațiu suplimentar în memorie. Un proces nou creat va avea spațiul virtual creat propriudar, pentru început, va folosi spațiul de memorie fizică al primului proces, prin intermediul copy-on-write. 
-    * Consultațfișierele ​''​process.c''​ și ''​thread.c''​. + 
-    * Se incrementează o variabilă globală dintr-un thread nou (''​thread.c''​) sau dintr-un proces nou (''​process.c''​)+Diferențe sesizabile se observă în cazul numărului de page fault-uri ​și al schimbărilor de context involuntare. Schimbările de context involuntare (la schimbarea cuantei) sunt mai numeroase în cadrul proceselor întrucât se schimbă contextul procesului curent cu procesele pe care le creează. În cazul thread-urilor nu există ​schimbări de context între ​thread-uri, deoarece aparțin aceluiași procesNumărul de page fault-uri în cadrul proceselor este cauzat de accesele de scriere proceselor noi peste zonele marcate copy-on-write. Sunt relativ puține accese, dar se resimt la nivelul numărului de page fault-uri. 
-    * Folosiți ​comanda ''​make'' ​pentru a obține executabilele ​''​thread''​ și ''​process''​. + 
-    * Rulați cele două executabile:<code bash> +==== Creare de thread-uri ​și spațiul de adresă ==== 
-./thread + 
-./process+Dorim să vedem cum afectează crearea thread-urilor spațiul de adresă al procesului. Pentru aceasta accesăm subdirectorul ​''​address-space/''​; urmărim conținutul ​fișierului ​''​address-space.c''​. În cadrul fișierului se creează 5 thread-uriLa începutul programului și după fiecare creare de thread se așteaptă apăsarea tastei ​''​ENTER'' ​de utilizator; în această vreme utilizatorul poate inspecta spațiul de adresă. Thread-urile afișează ​un mesaj simplu și apoi așteaptă ​''​100'' ​de secunde
 + 
 +Compilăm codul sursă folosind ​comanda ''​make''​. Rezultă executabilul ​''​address-space''​. 
 + 
 +Rulăm executabilul:<code bash> 
 +./address-space
 </​code>​ </​code>​
-      * Observați valoarea variabilei ''​data_var'',​ afișată prin rularea celor două executabile. + 
-      * Observați că thread-urile partajează secțiunea ​de date a procesului, ​în vreme ce procesele au o zonă de date proprie. +Pentru a vizualiza spațiul de adrese al procesului, ​folosim comanda ​''​pmap''​. ​Deschidem o altă consolă ​și rulăcomanda ''​pmap'' ​din output-ul căreia eliminăm referințele la biblioteci:<code bash> 
-  - Nevoie de acces exclusiv +pmap -p $(pidof address-space) | grep -v '/lib/'
-    * Intrați în directorul ​''​2-list/''​. +
-    * Consultați fișierele ''​thread-list-app.c''​ și ''​list.c''​. +
-    * Ce impact are macro-ul ''​USE_MUTEX''?​ +
-    * Parcurgeți fișierul ''​Makefile'' ​și urmăriți definirea macro-ului ''​USE_MUTEX''​. +
-    * Folosiți ​comanda ''​make'' ​pentru a obține executabilele ''​thread-list-app''​ și ''​thread-list-app-mutex''​. +
-    * Rulați ''​thread-list-app''​ de mai multe ori, până obțineți eroare. +
-      * Dacă nu vă dă eroare, actualizați macro-urile ''​NUM_THREADS''​ și ''​NUM_ROUNDS'' ​la alte valori (probabil mai mari), recompilați folosind ''​make''​ și apoi rulați ''​thread-list-app''​. +
-    * Rulați ''​thread-list-app-mutex''​ de mai multe ori. +
-      * De ce nu vă dă eroare? +
-    * Rulați ambele executabile sub comanda ''​time''​ (e relevant timpul obținut pentru rulare cu succes):<code bash> +
-time ./thread-list-app +
-time ./thread-list-app-mutex+
 </​code>​ </​code>​
-      * Care timp este mai mare? De ce? +Acum se afișează zonele de memorie aferente procesului înainte de crearea unui thread. 
-      * De ce timpul petrecut în kernel space (//system time// -- ''​sys''​ în output-ul ​''​time''​) este mai mare în cazul folosirii ​''​thread-list-app-mutex''​+ 
-  - Mutex vsSpinlock -- consum de timp +Apoi apăsăm ​''​ENTER''​ în prima consolă. Acum se va crea un thread. 
-    * Intrațîn directorul ​''​3-spin/''​+ 
-    * Consultațfișierul ''​spin.c''​+Investigăm din nou spațiul de adrese al procesului prin rularea comenzii ​''​pmap'' ​în forma de mai sus în a doua consolă. Observăm apariția unei zone de ''​8192K'' ​(adică 8MB)Apăsăm iarăși ''​ENTER'' ​în prima consolă șafișăm spațiul de adreseRepetăm procesul de încă 3 ori până la crearea tuturor celor 5 thread-uri. Observăm ​că pentru fiecare thread au fost create două zone: o zonă de ''​8192K''​ cu permisiuni de citire șscriere și încă o pagină de gardă (''​4K''​) fără permisiuni
-      * Urmărițefectul macro-ului ​''​USE_SPINLOCK''​. + 
-    * Consultați fișierul ​''​Makefile''​. +Zona de ''​8192K'' ​creată pentru fiecare thread este stiva acelui threadSe rezervă spațiu virtual pentru stiva fiecărui thread la creare. Observăm că pe un sistem pe 32 de biți, cu dimensiune relativ scăzută a spațiului de adreseo valoare implicită de ''​8192K'' ​a stivei unui thread va limita numărul de thread-uri care pot fi create, întrucât se umple spațiul de adrese.((Dimensiunea la creare a stivei unui thread poate fi schimbată de la valoarea implicită cu ajutorul apelului [[http://​man7.org/​linux/​man-pages/​man3/​pthread_attr_setstacksize.3.html|pthread_attr_setstacksize]].)) 
-      * Observați unde este definitca opțiune de compilaremacro-ul ​''​USE_SPINLOCK''​. +==== Partajare informație între procese șthread-uri ==== 
-    * Folosiți comanda ​''​make'' ​pentru a obține două executabile: ​''​spin''​ și ''​mutex''​. + 
-    * Rulați ​cele două executabile prin comanda ​''​time'' ​pentru a contabiliza timpul de rulare:<code bash> +Ne propunem să investigăm modul în care thread-urile partajează datele, iar procesele nu. Pentru aceasta accesăm subdirectorul ​''​shared-data/​''​; urmărim conținutul fișierelor ''​process.c''​ și ''​thread.c''​. În ambele fișiere se incrementează o variabilă globală (''​data_var''​) în cadrul unui proces nou și într-un thread nou. Variabila globală este inițializată la ''​0''​. 
-time ./spin + 
-time ./mutex+Compilăm ​cele două programe folosind ​''​make''​. Rezultă două fișiere în format executabil: process și thread. 
 + 
 +Rulăm cele două executabile:<code bash> 
 +./process ​ 
 +data_var = 1 
 +data_var = 1 
 +./thread  
 +data_var = 1 
 +data_var = 2
 </​code>​ </​code>​
-      * Care comandă a durat mai mult? De ce? + 
-      * De ce comanda ​''​./spin'' ​nu petrece foarte mult în kernel space (//system time// -- ''​sys'' ​în output-ul ''​time''​)? +Observăm că procesele au o secțiune de date proprie; fiecare incrementare s-făcut de la ''​0'' ​la ''​1''​. Thread-urile ​(aceluiași proces) partajează însă secțiunea de date. În acest caz thread-ul nou creat incrementează variabila de la ''​0'' ​la ''​1'',​ pe când thread-ul inițial incrementează variabila de la ''​1'' ​la ''​2''​. 
-  - Granularitatea accesului exclusiv + 
-    * Intrați în directorul ​''​4-granularity/''​+==== Stiva unui thread în spațiul de adresă ==== 
-    * Consultațfișierul ''​granularity.c''​. + 
-    * Urmăriți macro-urile ''​GRANULARITY_...''​. +Ne propunem să urmărim separația efectivă a stivelor thread-urilor aceluiașproces. Pentru aceasta accesăm subdirectorul ​''​stack-access/''​; urmărim conținutul ​fișierului ​''​stack-access.c''​. ​În cadrul fișierului se creează două thread-uri, unul care citește și altul care scrie într-o variabilă. 
-    * Compilați folosind comanda ​''​make''​; vețobține executabilul ​''​granularity''​. + 
-    * Rulați executabilul ​și măsurați timpul ​de execuție:<code bash> +Programul este făcut în așa fel încât să se întâmple următoarea secvență: 
-time ./granularity+  ​Thread-ul writer doarme preț de ''​2''​ secunde. 
 +  - Thread-ul reader inițializează variabila ''​local_var''​ la valoarea ''​0x11111111''​. 
 +  - Thread-ul reader inițializează variabila globală ''​stack_var_pointer''​ la adresa variabilei ''​local_var''​. 
 +  - Thread-ul reader afișează valoarea variabilei ''​local_var''​. 
 +  - Thread-ul reader doarme preț de ''​5'' ​secunde. 
 +  - Thread-ul writer modifică valorea zonei referite de ''​stack_var_pointer'',​ adică valoarea variabilei ''​local_var''​ la valoarea ''​0x22222222''​. 
 +  - Thread-ul writer îșîncheie execuția. 
 +  - Thread-ul reader afișează valoarea variabilei ​''​local_var''​. 
 + 
 +Programul de față arată că un thread poate accesa ​și scrie fără nici o problemă pe stiva altui thread cât timp știe unde este stiva (sau variabila de pe stivă). În cazul de față este vorba de variabila ''​local_var''​ locală thread-ului reader; adresa acestei variabile este stocată în variabila globală de tip pointer ''​stack_var_pointer''​ accesibilă și thread-ului writer. 
 + 
 +Pentru a testa programul compilăm folosind ''​make''​. Obținem fișierul în format executabil ''​stack-access''​ pe care îl rulăm:<code bash> 
 +./stack-access  
 +writer: going to sleep for 2 seconds ... 
 +reader: local_var is 0x11111111, local_var address is: 0x7fcaf4f90f4c 
 +reader: going to for 5 seconds ... 
 +writer: write 0x22222222 to reader local_var (address is 0x7fcaf4f90f4c) 
 +writer: end execution 
 +reader: local_var is 0x22222222 
 +reader: end execution
 </​code>​ </​code>​
-    * Modificați macro-ul ''​GRANULARITY_TYPE'' ​la valoarea ''​GRANULARITY_COURSE''​. + 
-    * Recompilați programul+Observăm că, la final, thread-ul reader a afișat valoarea variabilei locale (de pe stiva sa) ''​local_var'' ​iar valoarea ​acesteia este ''​0x22222222''​, valoare stabilită de thread-ul writerThread-ul writer a reușit să scrie în stiva thread-ului reader. Acest lucru este posibil pentru că thread-urile partajează spațiul de adrese al procesului din care fac parte; deșfiecare thread are stiva sa, accesul unui thread la stiva altuia nu este protejat de sistemul de operare; ambele stive se găsesc în același spațiu de adresă și orice thread le poate modifica pe oricare dintre ele
-    * Rulați executabilul ​și măsurați, din nou, timpul de execuție:<code bash> +==== Utilitate apeluri reentrante ==== 
-time ./granularity+ 
 +Dorim să validăm importanța apelurilor reentrante în lucrul cu thread-uri. Pentru aceasta accesăm subdirectorul ''​reentrant/'';​ parcurgem fișierul cod sursă ''​rentrant.c''​. Fișierul creează ''​NUM_THREADS''​ thread-uri ​și timp de ''​NUM_THREADS''​ runde apelează funcția nereentrantă [[http://​man7.org/​linux/​man-pages/​man3/​ctime.3.html|ctime]] sau varianta reentrantă a acesteia [[http://​man7.org/​linux/​man-pages/​man3/​ctime.3.html|ctime_r]]. 
 + 
 +Compilăprogramul folosind comanda ''​make''​. În urma compilării rezultă două fișiere în format executabil:​ 
 +  * Fișierul ''​reentrant''​ folosește funcția ''​ctime''​ (varianta nereentrantă). 
 +  * Fișierul ''​reentrant-ok''​ folosește funcția ''​ctime_r''​ (varianta reentrantă). 
 + 
 +Rulăm cele două executabile cu redirectarea output-ului într-un fișier:<code bash> 
 +./reentrant > out 
 +./​reentrant-ok > out-ok
 </​code>​ </​code>​
-    * Ce observați? Cum explicați diferența între timpi? + 
-    * Care este avantajul ​și dezavantajul fiecărei soluții?+Urmărim diferențele între cele două fișiere:<​code bash> 
 +$ ls -l 
 +total 9596 
 +[...] 4780850 Apr  8 00:47 out 
 +[...] 4992000 Apr  8 00:48 out-ok 
 +</​code>​ 
 +Observăm că fișierul de ieșire în cazul executabilului cu varianta nereentrantă are dimensiunea mai mică, deci absența reentranței a cauzat incoerența datelor obținute. Acest lucru se întâmplă întrucât, în momentul în care un thread folosește funcția ''​printf''​ pentru a afișa bufferul ''​str_time[i]''​ un alt thread îl poate modifica chiar în acel moment, iar datele devin incoerente. 
 + 
 +Dacă folosim comanda ''​file''​ vom vedea că se raportează că fișierul ''​out''​ este conținut binar, încă o dovadă a incoerenței informațiilor furnizate:<​code bash> 
 +$ file out 
 +out: data 
 +$ file out-ok  
 +out-ok: ASCII text 
 +</​code>​ 
 + 
 +În cazul folosirii programelor cu thread-uri, trebuie folosite versiunile reentrante ale funcțiilor de bibliotecă. 
 + 
 +==== Implementare de thread-uri în Java șPython ==== 
 + 
 +În Java și Python implementările de thread-uri sunt cu suport din partea sistemului de operare. Adică o operație de creare ​unui thread rezultă în crearea unui thread în kernel space (kernel-level threads). 
 + 
 +Pentru a verifica acest lucru accesăm subdirectorul ''​lang/''​ unde avem fișierul sursă ''​MultithreadingTest.java'' ​și ''​threading_demo.py''​. Pentru a compila fișierul ''​MultithreadingTest.java''​ folosim comanda ''​make''​. 
 + 
 +Rularea ambelor programe duce la crearea de a câte unui thread în momentul apăsării tastei ''​ENTER''​. Pentru Java rulăm comanda: 
 +<​code>​ 
 +$ java MultithreadingTest 
 +Press ENTER to create new thread ... 
 + 
 +Press ENTER to create new thread ... 
 +Thread 12 is running 
 + 
 +Press ENTER to create new thread ... 
 +Thread 13 is running 
 + 
 +Press ENTER to create new thread ... 
 +Thread 14 is running 
 + 
 +Press ENTER to create new thread ... 
 +Thread 15 is running 
 +[...] 
 +</​code>​ 
 +Durează ''​SLEEP_TIME''​ secunde ca thread-urile să își încheie execuția. 
 + 
 +Pentru Python rulăm: 
 +<​code>​ 
 +$ python threading_demo.py 
 +Press ENTER to create new thread ... 
 + 
 +Thread 140481023772416 is runningPress ENTER to create new thread ... 
 + 
 + 
 +Thread 140481015379712 is running 
 +Press ENTER to create new thread ... 
 + 
 +Press ENTER to create new thread ... 
 + ​Thread 140481006987008 is running 
 + 
 +Thread 140480796948224 is running 
 + Press ENTER to create new thread ... 
 +[...] 
 +</​code>​ 
 +La fel, durează ''​SLEEP_TIME''​ secunde ca thread-urile să își încheie execuția. 
 + 
 +Pentru a verifica faptul că sunt într-adevăr create thread-uri noi rulăm în altă terminal comenzile de mai jos pentru Java: 
 +<​code>​ 
 +$ ps -efL | grep Multithreading | wc -l 
 +35 
 +$ ps -efL | grep Multithreading | wc -l 
 +36 
 +$ ps -efL | grep Multithreading | wc -l 
 +37 
 +$ ps -efL | grep Multithreading | wc -l 
 +43 
 +</​code>​ 
 +Observăm creșterea numărului de thread-uri pe măsură ce le creăm folosind opțiunea ''​-L''​ a comenzii ''​ps''​ (pentru //​lightweight process//​). 
 + 
 +La fel procedăm și pentru Python: 
 +<​code>​ 
 +$ ps -efL | grep threading_demo | wc -l 
 +
 +$ ps -efL | grep threading_demo | wc -l 
 +
 +$ ps -efL | grep threading_demo | wc -l 
 +
 +</​code>​ 
 + 
 +De avut în vedere că în Python, deși thread-urile au suport în kernel și au potențialul de a rula simultan, nu vor face aceste lucru din cauza unui lock global din interpretorul Python numit GIL ([[https://​wiki.python.org/​moin/​GlobalInterpreterLock|Global Interpreter Lock]]). Este vorba de CPython, intepretorul de referință Python. IronPython nu are GIL și poate rula cu adevărat în paralel thread-uri cu suport de la nivelul nucleului.
so/cursuri/curs-08.1365516654.txt.gz · Last modified: 2013/04/09 17:10 by razvan.deaconescu
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