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:47]
razvan.deaconescu [Creare de thread-uri și spațiu de adresă]
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 53: Line 57:
 ==== Creare de thread-uri și spațiul de adresă ==== ==== Creare de thread-uri și spațiul de adresă ====
  
-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-urilor ​afișează un mesaj simplu și apoi așteaptă ''​100''​ de secunde.+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''​. Compilăm codul sursă folosind comanda ''​make''​. Rezultă executabilul ''​address-space''​.
Line 73: Line 77:
 ==== 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 86: 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.1396903657.txt.gz · Last modified: 2014/04/07 23:47 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