Differences

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

Link to this comparison view

so:cursuri:curs-08 [2014/04/07 23:18]
razvan.deaconescu [Partajare informație între procese și thread-uri]
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 =====
Line 51: Line 55:
 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 proces. Numă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. 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 proces. Numă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.
  
-==== Creare de thread-uri și spațiu de adresă ====+==== Creare de thread-uri și spațiul de adresă ====
  
-TODO+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-uri. La î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>​
 +
 +Pentru a vizualiza spațiul de adrese al procesului, folosim comanda ''​pmap''​. Deschidem o altă consolă și rulăm comanda ''​pmap''​ din output-ul căreia eliminăm referințele la biblioteci:<​code bash>
 +pmap -p $(pidof address-space) | grep -v '/​lib/'​
 +</​code>​
 +Acum se afișează zonele de memorie aferente procesului înainte de crearea unui thread.
 +
 +Apoi apăsăm ''​ENTER''​ în prima consolă. Acum se va crea un thread.
 +
 +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ă și afișăm spațiul de adrese. Repetă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 și scriere și încă o pagină de gardă (''​4K''​) fără permisiuni.
 +
 +Zona de ''​8192K''​ creată pentru fiecare thread este stiva acelui thread. Se 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 adrese, o 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]].))
 ==== Partajare informație între procese și thread-uri ==== ==== Partajare informație între procese și thread-uri ====
  
-Ne propunem să investigăm modul în care thread-urile partajează datele, iar procesele nu. Pentru aceasta accesăm subdirectorul ''​creation-time/'';​ 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''​.+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''​.
  
 Compilăm cele două programe folosind ''​make''​. Rezultă două fișiere în format executabil: process și thread. Compilăm cele două programe folosind ''​make''​. Rezultă două fișiere în format executabil: process și thread.
Line 70: Line 90:
 </​code>​ </​code>​
  
-Observăm că procesele au o secțiune de date proprie; fiecare incrementare s-a făcut de la ''​0''​ la ''​1''​. Thread-urile (aceluiași proces) partajează însă secțiunea de date. În acest caz un 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''​.+Observăm că procesele au o secțiune de date proprie; fiecare incrementare s-a 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''​.
  
 ==== Stiva unui thread în spațiul de adresă ==== ==== Stiva unui thread în spațiul de adresă ====
  
-TODO+Ne propunem să urmărim separația efectivă a stivelor thread-urilor aceluiași 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ă.
  
 +Programul este făcut în așa fel încât să se întâmple următoarea secvență:
 +  - 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 își î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>​
 +
 +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 writer. Thread-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și 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.
 ==== Utilitate apeluri reentrante ==== ==== Utilitate apeluri reentrante ====
  
-TODO+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ăm 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>​ 
 + 
 +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 și Python ==== 
 + 
 +În Java și Python implementările de thread-uri sunt cu suport din partea sistemului de operare. Adică o operație de creare a 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.1396901924.txt.gz · Last modified: 2014/04/07 23:18 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