This shows you the differences between two versions of the page.
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&lock_to_path=0&autoplay=0&autohide_ctrls=0&features=undefined&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&lock_to_path=0&autoplay=0&autohide_ctrls=0&features=undefined&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 | ||
+ | 2 | ||
+ | $ ps -efL | grep threading_demo | wc -l | ||
+ | 4 | ||
+ | $ ps -efL | grep threading_demo | wc -l | ||
+ | 8 | ||
+ | </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. |