Differences

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

Link to this comparison view

so:cursuri:curs-09 [2014/04/14 21:13]
razvan.deaconescu [Deadlock]
so:cursuri:curs-09 [2019/04/15 01:04] (current)
razvan.deaconescu
Line 1: Line 1:
 ====== Curs 09 - Sincronizare ====== ====== Curs 09 - Sincronizare ======
  
-<​html>​ +  * [[http://​elf.cs.pub.ro/​so/​res/​cursuri/​SO%20-%20Curs%2009%20-%20Sincronizare.pdf|Curs 09 - Sincronizare (PDF)]]
-    <iframe src="​http://​docs.google.com/​viewer?​url=http://​elf.cs.pub.ro/​so/​res/​cursuri/​SO_Curs-09.pdf&​embedded=true"​ width="​600"​ height="​480"​ style="​border:​ none;">​ +
-    </​iframe>​ +
-</​html>​+
  
-  * [[http://elf.cs.pub.ro/so/​res/​cursuri/​SO_Curs-09.pdf ​Curs 09 - Sincronizare (PDF)]]+  * [[https://drive.google.com/open?​id=1PCJp2UD0KSuAOFJ9ZMkB_lMhR9jJ4exD7Pgm-vzSrQA|Notițe de curs]]
  
   * Suport curs   * Suport curs
-    * Operating Systems Concepts+    * Operating Systems Concepts ​Essentials
       * Capitolul 6 - Process Synchronization       * Capitolul 6 - Process Synchronization
     * Modern Operating Systems     * Modern Operating Systems
Line 15: Line 12:
         * Secțiunea 2.3 - Interprocess Communication         * Secțiunea 2.3 - Interprocess Communication
     * [[http://​greenteapress.com/​semaphores/​|Allen B. Downey - The Little Book of Semaphores]]     * [[http://​greenteapress.com/​semaphores/​|Allen B. Downey - The Little Book of Semaphores]]
 +    * [[https://​deadlockempire.github.io/​|The Deadlock Empire: Slay dragons, master concurency!]]
 +  * [[http://​elf.cs.pub.ro/​so/​res/​cursuri/​SO_Curs-09.pdf|Curs 09 anterior]]
 +
 +
 +<​html>​
 +  <​center>​
 +    <iframe src="​https://​docs.google.com/​viewer?​url=https://​elf.cs.pub.ro/​so/​res/​cursuri/​SO%20-%20Curs%2009%20-%20Sincronizare.pdf&​embedded=true"​ width="​600"​ height="​480"​ style="​border:​ none;">​
 +    </​iframe>​
 +  </​center>​
 +</​html>​
  
 ===== Demo-uri ===== ===== Demo-uri =====
Line 44: Line 51:
 </​note>​ </​note>​
  
-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.+Eroarea ​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:<​code bash> 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:<​code bash>
Line 58: Line 65:
 Compilăm cele două programe folosind ''​make''​. Rezultă două fișiere în format executabil: ''​spinlock''​ și ''​mutex''​. Compilăm cele două programe folosind ''​make''​. Rezultă două fișiere în format executabil: ''​spinlock''​ și ''​mutex''​.
  
-Cele două fișiere au fost generat ​din același cod sursă (''​spinlock-mutex.c''​),​ după cum a fost definit sau nu macro-ul ''​USE_SPINLOCK''​. Macro-ul îl definim în fișierul ''​Makefile''​.+Cele două fișiere au fost generate ​din același cod sursă (''​spinlock-mutex.c''​),​ după cum a fost definit sau nu macro-ul ''​USE_SPINLOCK''​. Macro-ul îl definim în fișierul ''​Makefile''​.
  
 Pentru a contabiliza timpul de rulare rulăm cele două executabile prin comanda ''​time'':<​code bash> Pentru a contabiliza timpul de rulare rulăm cele două executabile prin comanda ''​time'':<​code bash>
Line 92: Line 99:
 adică o condiție de cursă de tipul TOCTTOU, în care se poate "​infiltra"​ alt thread. Practic este posibil ca două thread-uri să scadă valoarea variabilei ''​num_items''​ deși condiția ar fi trebuit să se întâmple doar pentru unul. Dacă avem o valoare inițială ''​NUM_ITEMS_CONSUMER + 2'',​ atunci există riscul ca mai multe thread-uri să vadă îndeplinită condiția și apoi toate să scadă valoarea variabilei ''​num_items''​ rezultând într-o valoare negativă. adică o condiție de cursă de tipul TOCTTOU, în care se poate "​infiltra"​ alt thread. Practic este posibil ca două thread-uri să scadă valoarea variabilei ''​num_items''​ deși condiția ar fi trebuit să se întâmple doar pentru unul. Dacă avem o valoare inițială ''​NUM_ITEMS_CONSUMER + 2'',​ atunci există riscul ca mai multe thread-uri să vadă îndeplinită condiția și apoi toate să scadă valoarea variabilei ''​num_items''​ rezultând într-o valoare negativă.
  
-O astfel de situația poate duce la un comportament nedeterminst al programului,​ la coruperea datelor, chiar la încheierea cu eroare a programului. Mai mult o astfel de situație poate fi exploatată de un atacator.+O astfel de situație poate duce la un comportament nedeterminst al programului,​ la coruperea datelor, chiar la încheierea cu eroare a programului. Mai mult o astfel de situație poate fi exploatată de un atacator.
  
 Pentru a preveni apariția condițiilor de cursă, trebuie implementată corespunzător sincronizarea accesului. Din păcate, condițiile de cursă pot apărea foarte greu (adică programul să fie greșit dar să meargă **aproape** tot timpul); acest lucru face nedeterminist comportamentul programului și dificilă investigarea problemei. Pentru a preveni apariția condițiilor de cursă, trebuie implementată corespunzător sincronizarea accesului. Din păcate, condițiile de cursă pot apărea foarte greu (adică programul să fie greșit dar să meargă **aproape** tot timpul); acest lucru face nedeterminist comportamentul programului și dificilă investigarea problemei.
Line 108: Line 115:
 Deadlock-ul are loc pentru că cele două mutex-uri folosite în program (''​xmutex''​ și ''​ymutex''​) nu sunt achiziționate în aceeași ordine. Unele thread-uri (cele care execută funcția ''​xfirst''​) achiziționează mutex-urile în ordinea ''​xmutex'',​ ''​ymutex'';​ celelalte thread-uri (cele care execută funcția ''​yfirst''​) achiziționează mutex-urile în ordinea ''​ymutex'',​ ''​xmutex''​. În acest fel, la un moment dat un thread va achiziționa mutex-ul ''​xfirst''​ și imediat după un altul va achiziționa ''​ymutex''​. În continuare ambele thread-uri vor aștepta după un mutex deținut de alt thread. Deadlock-ul are loc pentru că cele două mutex-uri folosite în program (''​xmutex''​ și ''​ymutex''​) nu sunt achiziționate în aceeași ordine. Unele thread-uri (cele care execută funcția ''​xfirst''​) achiziționează mutex-urile în ordinea ''​xmutex'',​ ''​ymutex'';​ celelalte thread-uri (cele care execută funcția ''​yfirst''​) achiziționează mutex-urile în ordinea ''​ymutex'',​ ''​xmutex''​. În acest fel, la un moment dat un thread va achiziționa mutex-ul ''​xfirst''​ și imediat după un altul va achiziționa ''​ymutex''​. În continuare ambele thread-uri vor aștepta după un mutex deținut de alt thread.
  
-<​note ​tip>+<​note ​important>
 Pentru a evita apariția deadlock-urilor în momentul în care folosim lock-uri/​mutex-uri,​ o condiție importantă este ca lock-urile să fie achiziționate în aceeași ordine. Pentru a evita apariția deadlock-urilor în momentul în care folosim lock-uri/​mutex-uri,​ o condiție importantă este ca lock-urile să fie achiziționate în aceeași ordine.
 </​note>​ </​note>​
Line 116: Line 123:
  
 Compilăm programul folosind ''​make''​. Rezultă fișierul ''​indefinite-wait''​ în format executabil. Compilăm programul folosind ''​make''​. Rezultă fișierul ''​indefinite-wait''​ în format executabil.
 +
 +Pentru a observa comportamentul programului îl rulăm:<​code bash>
 +./​indefinite-wait
 +</​code>​
 +Observăm că programul se blochează, deci fie avem deadlock fie unele thread-uri așteaptă fără a fi trezite/​notificate. Având un singur mutex folosit corezpunzător,​ problema nu poate fi deadlock deci este vorba de o așteptare nedefinită.
 +
 +La o investigație atentă observăm că atât thread-urile producător cât și cele consumator vor aștepta la variabilele condiție aferente (''​buffer_empty_cond''​ și ''​buffer_full_cond''​) fără a fi trezite. Nu există apeluri ale funcției [[http://​pubs.opengroup.org/​onlinepubs/​7908799/​xsh/​pthread_cond_signal.html|pthread_cond_signal]] necesare pentru trezirea thread-urilor.
 +
 +Rezolvăm această problemă prin adăugarea apelurilor necesare în cadrul funcțiilor ''​produce''​ și ''​consume''​ rezultând forma actualizată:<​code c>
 +static void *produce(void *arg)
 +{
 + size_t idx = (size_t) arg;
 +
 + pthread_mutex_lock(&​pc_mutex);​
 + if (pc_buffer.size >= MAX_ITEMS)
 + pthread_cond_wait(&​buffer_full_cond,​ &​pc_mutex);​
 + pc_buffer.storage[pc_buffer.size] = ITEM;
 + pc_buffer.size++;​
 + pthread_cond_signal(&​buffer_empty_cond);​
 + printf("​Producer %zu created item.\n",​ idx);
 + pthread_mutex_unlock(&​pc_mutex);​
 +
 + return NULL;
 +}
 +
 +static void *consume(void *arg)
 +{
 + size_t idx = (size_t) arg;
 +
 + pthread_mutex_lock(&​pc_mutex);​
 + if (pc_buffer.size == 0)
 + pthread_cond_wait(&​buffer_empty_cond,​ &​pc_mutex);​
 + pc_buffer.storage[pc_buffer.size] = NO_ITEM;
 + pc_buffer.size--;​
 + pthread_cond_signal(&​buffer_full_cond);​
 + printf("​Consumer %zu removed item.\n",​ idx);
 + pthread_mutex_unlock(&​pc_mutex);​
 +
 + return NULL;
 +}
 +</​code>​
 +În cele de mai sus, înainte de operația de unlock pe mutex am apelat funcția [[http://​pubs.opengroup.org/​onlinepubs/​7908799/​xsh/​pthread_cond_signal.html|pthread_cond_signal]]. Cu ajutorul acestei funcții, thread-ul producător ce tocmai a produs un element trezește un thread consumator care acum are ce consuma; în mod similar thread-ul consumator care a consumat un element trezește un thread producător care acum are loc unde să producă un element.
 +
 +După actualizarea programului,​ recompilăm folosind ''​make''​ și apoi îl rulăm. Acum programul va funcționa corespunzător fără problema așteptării nedefinite.
 ==== Granularitate regiune critică ==== ==== Granularitate regiune critică ====
  
Line 129: Line 180:
 </​code>​ </​code>​
  
-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).+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).
  
 <​note>​ <​note>​
 În general preferăm granularitate fină pentru regiunile critice (adică regiuni critice de mică dimensiune);​ dar granularitatea fină poate înseamna operații foarte dese pe mutex (//lock contention//​). De aceea este recomandat ca folosirea granularității fine să fie echilibrată de un număr redus de thread-uri care să dorească să acceseze la un moment dat regiunea critică, pentru a genera cât mai puțină încărcare (//​contention//​) pe mutex. În general preferăm granularitate fină pentru regiunile critice (adică regiuni critice de mică dimensiune);​ dar granularitatea fină poate înseamna operații foarte dese pe mutex (//lock contention//​). De aceea este recomandat ca folosirea granularității fine să fie echilibrată de un număr redus de thread-uri care să dorească să acceseze la un moment dat regiunea critică, pentru a genera cât mai puțină încărcare (//​contention//​) pe mutex.
 </​note>​ </​note>​
- 
so/cursuri/curs-09.1397499227.txt.gz · Last modified: 2014/04/14 21:13 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