Mutex

Versiunea actuală a paginii nu a fost încă examinată de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită la 31 august 2021; verificările necesită 5 modificări .

Mutex ( în engleză  mutex , de la excluderea reciprocă  - „excluderea reciprocă”) este o primitivă de sincronizare care asigură excluderea reciprocă a execuției secțiunilor critice de cod [1] . Un mutex clasic diferă de un semafor binar prin prezența unui proprietar exclusiv, care trebuie să-l elibereze (adică să-l transfere într-o stare deblocat) [2] . Un mutex diferă de un spinlock prin trecerea controlului către planificator pentru a comuta firele de execuție atunci când mutexul nu poate fi achiziționat [3] . Există, de asemenea , blocări de citire-scriere numite mutexuri partajate care oferă, pe lângă blocarea exclusivă, o blocare partajată care permite proprietatea comună a mutexului dacă nu există un proprietar exclusiv [4] .

În mod convențional, un mutex clasic poate fi reprezentat ca o variabilă care poate fi în două stări: blocat și deblocat. Când un thread intră în secțiunea sa critică, apelează o funcție pentru a bloca mutexul, blocând firul până când mutex-ul este eliberat dacă un alt thread îl deține deja. La ieșirea din secțiunea critică, firul apelează funcția pentru a muta mutex-ul în starea deblocat. Dacă există mai multe fire de execuție blocate de un mutex în timpul deblocării, planificatorul selectează un fir de execuție pentru a relua execuția (în funcție de implementare, acesta poate fi fie un fir aleator, fie un fir determinat de anumite criterii) [5] .

Sarcina unui mutex este să protejeze obiectul de a fi accesat de alte fire decât cel care deține mutex-ul. În orice moment, doar un fir poate deține un obiect protejat de un mutex. Dacă un alt thread are nevoie de acces la datele protejate de mutex, atunci acel fir se va bloca până când mutex-ul este eliberat. Un mutex protejează datele de a fi corupte de modificări asincrone ( o condiție de cursă ), dar alte probleme, cum ar fi blocarea sau captura dublă, pot fi cauzate dacă sunt utilizate incorect .

După tipul de implementare, mutex-ul poate fi rapid, recursivsau cu controlul erorilor.

Probleme de utilizare

Inversie prioritară

O inversare a priorității are loc atunci când ar trebui să se execute un proces cu prioritate mare, dar se blochează pe un mutex deținut de procesul cu prioritate scăzută și trebuie să aștepte până când procesul cu prioritate scăzută deblochează mutexul. Un exemplu clasic de inversare a priorității fără restricții în sistemele în timp real este atunci când un proces cu o prioritate medie se ocupă de timpul CPU, drept urmare procesul cu o prioritate scăzută nu poate rula și nu poate debloca mutex-ul [6] .

O soluție tipică a problemei este moștenirea priorității, în care un proces care deține un mutex moștenește prioritatea unui alt proces blocat de acesta, dacă prioritatea procesului blocat este mai mare decât cea a celui curent [6] .

Programarea aplicațiilor

Mutexuri în API-ul Win32

API-ul Win32 din Windows are două implementări de mutexuri - mutexurile înșiși, care au nume și sunt disponibile pentru utilizare între diferite procese [7] , și secțiuni critice , care pot fi utilizate numai în cadrul aceluiași proces de către fire diferite [8] . Fiecare dintre aceste două tipuri de mutexuri are propriile sale funcții de captare și eliberare [9] . Secțiunea critică pe Windows este puțin mai rapidă și mai eficientă decât mutexul și semaforul, deoarece utilizează instrucțiunile de testare și setare specifice procesorului [8] .

Mutexuri în POSIX

Pachetul Pthreads oferă diverse funcții care pot fi folosite pentru a sincroniza firele [10] . Printre aceste funcții se numără și funcții pentru lucrul cu mutexuri. În plus față de funcțiile de achiziție și eliberare mutex, este furnizată o funcție de încercare de achiziție mutex care returnează o eroare dacă este de așteptat o blocare a firului. Această funcție poate fi utilizată într-o buclă de așteptare activă dacă este nevoie [11] .

Funcțiile pachetului Pthreads pentru lucrul cu mutexuri
Funcţie Descriere
pthread_mutex_init() Crearea unui mutex [11] .
pthread_mutex_destroy() Distrugerea mutexului [11] .
pthread_mutex_lock() Transferarea unui mutex într-o stare blocată (captură mutex) [11] .
pthread_mutex_trylock() Încercați să puneți mutex-ul în starea blocată și returnați o eroare dacă firul ar trebui să se blocheze deoarece mutex-ul are deja un proprietar [11] .
pthread_mutex_timedlock() Încercați să mutați mutex-ul în starea blocată și returnați o eroare dacă încercarea a eșuat înainte de ora specificată [12] .
pthread_mutex_unlock() Transferarea mutexului în starea deblocat (eliberarea mutexului) [11] .

Pentru a rezolva probleme de specialitate, mutexurilor li se pot atribui diverse atribute [11] . Prin atribute, folosind funcția pthread_mutexattr_settype(), puteți seta tipul mutexului, care va afecta comportamentul funcțiilor de captare și eliberare a mutexului [13] . Un mutex poate fi unul din trei tipuri [13] :

Mutexuri în C

Standardul C17 al limbajului de programare C definește un tip mtx_t[15] și un set de funcții pentru a lucra cu acesta [16] care trebuie să fie disponibil dacă macro- __STDC_NO_THREADS__ul nu a fost definit de compilator [15] . Semantica și proprietățile mutexurilor sunt în general în concordanță cu standardul POSIX.

Tipul mutex este determinat prin trecerea unei combinații de steaguri la funcția mtx_init()[17] :

Posibilitatea de a utiliza mutexuri prin intermediul memoriei partajate prin diferite procese nu este luată în considerare în standardul C17.

Mutexuri în C++

Standardul C++17 al limbajului de programare C++ definește 6 clase diferite de mutex [20] :

Biblioteca Boost oferă, în plus, mutexuri denumite și inter-procese, precum și mutexuri partajate, care permit achiziționarea unui mutex pentru proprietate partajată de mai multe fire de date de numai citire, fără excluderea scrierii pe durata achiziției de blocare, care este în esență un mecanism de blocare de citire-scriere [25] .

Detalii de implementare

La nivelul sistemelor de operare

În cazul general, mutexul stochează nu numai starea sa, ci și o listă de sarcini blocate. Schimbarea stării unui mutex poate fi implementată folosind operații atomice dependente de arhitectură la nivel de cod de utilizator, dar la deblocarea mutex-ului, trebuie reluate și alte sarcini care au fost blocate de mutex. În aceste scopuri, o primitivă de sincronizare de nivel inferior este bine potrivită - futex , care este implementată pe partea sistemului de operare și preia funcționalitatea sarcinilor de blocare și deblocare, permițând, printre altele, crearea mutexurilor interprocese [26] . În special, folosind futex, mutex-ul este implementat în pachetul Pthreads în multe distribuții Linux [27] .

Pe arhitecturile x86 și x86_64

Simplitatea mutex-urilor le permite să fie implementate în spațiul utilizatorului folosind o instrucțiune de asamblare XCHGcare poate copia atomic valoarea mutex-ului într-un registru și, simultan, setează valoarea mutex-ului la 1 (scrisă anterior în același registru). O valoare mutex de zero înseamnă că este în starea blocată, în timp ce o valoare de unu înseamnă că este în starea deblocat. Valoarea din registru poate fi testată pentru 0, iar în cazul unei valori zero, controlul trebuie returnat programului, ceea ce înseamnă că mutex-ul este dobândit, dacă valoarea a fost diferită de zero, atunci controlul trebuie transferat către planificatorul să reia activitatea unui alt fir, urmat de o a doua încercare de a achiziționa mutexul, care servește ca analog al blocării active. Un mutex este deblocat prin stocarea valorii 0 în mutex folosind comanda XCHG[28] . Alternativ, LOCK BTS(implementarea TSL pentru un bit) sau CMPXCHG[29] ( implementarea CAS ) pot fi utilizate.

Transferul controlului către planificator este suficient de rapid încât să nu existe o buclă de așteptare activă, deoarece CPU-ul va fi ocupat cu executarea unui alt fir și nu va fi inactiv. Lucrul în spațiul utilizatorului vă permite să evitați apelurile de sistem care sunt costisitoare în ceea ce privește durata procesorului [30] .

În arhitectura ARM

Arhitectura ARMv7 folosește așa-numitele monitoare locale și globale exclusive pentru a sincroniza memoria între procesoare, care sunt mașini de stare care controlează accesul atomic la celulele de memorie [31] [32] . O citire atomică a unei celule de memorie poate fi efectuată folosind instrucțiunea LDREX[33] , iar o scriere atomică se poate face prin instrucțiunea STREX, care returnează și steag-ul de succes al operației [34] .

Algoritmul de captare mutex implică citirea valorii sale cu LDREXși verificarea valorii citite pentru o stare blocată, care corespunde valorii 1 a variabilei mutex. Dacă mutex-ul este blocat, se apelează codul de așteptare pentru eliberarea blocării. Dacă mutex-ul era în starea deblocat, atunci blocarea ar putea fi încercată folosind instrucțiunea de scriere exclusivă STREXNE. Dacă scrierea eșuează deoarece valoarea mutexului s-a schimbat, atunci algoritmul de captare se repetă de la început [35] . După capturarea mutexului, se execută instrucțiunea DMB, care garantează integritatea memoriei resursei protejate de mutex [36] .

Înainte ca mutex-ul să fie eliberat, instrucțiunea este numită și DMB, după care valoarea 0 este scrisă în variabila mutex folosind instrucțiunea STR, ceea ce înseamnă transfer în starea deblocat. După ce mutex-ul este deblocat, sarcinile de așteptare, dacă există, ar trebui să fie semnalate că mutex-ul a fost eliberat [35] .

Vezi și

Note

  1. Tanenbaum, 2011 , 2.3.6. Mutexuri, p. 165.
  2. Oleg Tsiliurik. Instrumente de programare kernel: Partea 73. Paralelism și sincronizare. Încuietori. Partea 1 . - www.ibm.com, 2013. - 13 august. — Data accesului: 06.12.2019.
  3. Specificațiile de bază ale grupului deschis numărul 7, ediția 2018, Rationale for System Interfaces,  Informații generale . Preluat la 20 iunie 2020. Arhivat din original la 18 iunie 2020.
  4. 1 2 3 C++17, 2017 , 33.4.3.4 Tipuri de mutex partajate, p. 1373.
  5. Tanenbaum, 2011 , 2.3.6. Mutexuri, p. 165-166.
  6. ↑ 1 2 Steven Rostedt, Alex Shi. Proiectarea implementării RT-mutex - Documentația Linux Kernel  . Documentația Linux Kernel . Comunitatea de dezvoltare a nucleului (7 iunie 2017). Preluat la 16 iunie 2020. Arhivat din original la 16 iunie 2020.
  7. Creați Mutex . Data accesului: 20 decembrie 2010. Arhivat din original la 14 februarie 2012.
  8. ↑ 1 2 Michael Satran, Drew Batchelor. Obiecte secțiuni critice  . Documentatie . Microsoft (31 mai 2018). Data accesului: 20 decembrie 2010. Arhivat din original la 14 februarie 2012.
  9. Michael Satran, Drew batchelor. Funcții de sincronizare - aplicații Win32  . Documentatie . Microsoft (31 mai 2018). Preluat la 18 iunie 2020. Arhivat din original la 18 iunie 2020.
  10. Tanenbaum, 2011 , 2.3.6. Mutexuri, Mutexuri în Pthreads, p. 167.
  11. 1 2 3 4 5 6 7 Tanenbaum, 2011 , 2.3.6. Mutexuri, Mutexuri în Pthreads, p. 168.
  12. IEEE, Grupul deschis. pthread_mutex_timedlock  (engleză) . pubs.opengroup.org . Grupul deschis (2018). Preluat la 18 iunie 2020. Arhivat din original la 18 iunie 2020.
  13. ↑ 1 2 IEEE, Grupul deschis. pthread_mutexattr_settype(3  ) . Specificațiile de bază ale grupului deschis numărul 7, ediția 2018 . Grupul deschis (2018). Data accesului: 20 decembrie 2010. Arhivat din original la 14 februarie 2012.
  14. ↑ 1 2 3 IEEE, Grupul deschis. pthread_mutex_lock  (engleză) . Specificațiile de bază ale grupului deschis numărul 7, ediția 2018 . Grupul deschis (2018). Preluat la 17 iunie 2020. Arhivat din original la 17 septembrie 2019.
  15. 1 2 C17, 2017 , 7.26 Fire <fire.h>, p. 274.
  16. C17, 2017 , 7.26.4 Funcții Mutex, p. 277-279.
  17. C17, 2017 , 7.26.4.2 Funcția mtx_init, p. 277-278.
  18. C17, 2017 , 7.26.1 Introducere, p. 274.
  19. 1 2 C17, 2017 , 7.26.1 Introducere, p. 275.
  20. C++17, 2017 , 33.4.3.2 Tipuri Mutex, p. 1368.
  21. C++17, 2017 , 33.4.3.2.1 Clasa mutex, p. 1369-1370.
  22. C++17, 2017 , 33.4.3.2.2 Clasa recursive_mutex, p. 1370.
  23. C++17, 2017 , 33.4.3.3 Tipuri de mutex temporizate, p. 1370-1371.
  24. C++17, 2017 , 33.4.3.3.2 Clasa recursive_timed_mutex, p. 1372-1373.
  25. Mecanisme de sincronizare  . Boost C++ Libraries 1.73.0 . Preluat la 18 iunie 2020. Arhivat din original la 18 iunie 2020.
  26. Ulrich Drapper. Futexurile sunt complicate  : [ arh. 24.06.2020 ] : [PDF]. - Red Hat, Inc., 2005. - 11 decembrie.
  27. Karim Yaghmour, Jon Masters, Gilad Ben-Yossef, Philippe Gerum. Construirea de sisteme Linux încorporate: concepte, tehnici, trucuri și capcane . - „O'Reilly Media, Inc.”, 2008. - S. 400. - 466 p. - ISBN 978-0-596-55505-4 .
  28. Tanenbaum, 2011 , 2.3.6. Mutexuri, p. 166.
  29. Steven Rostedt. Proiectarea implementării RT-mutex  . Documentația Linux Kernel (7 iunie 2017). Preluat la 30 august 2021. Arhivat din original la 13 august 2021.
  30. Tanenbaum, 2011 , 2.3.6. Mutexuri, p. 166-167.
  31. ARM, 2009 , 1.2.1 LDREX și STREX, p. patru.
  32. ARM, 2009 , 1.2.2 Monitoare exclusive, p. 5.
  33. ARM, 2009 , 1.2.1 LDREX și STREX, LDREX, p. patru.
  34. ARM, 2009 , 1.2.1 LDREX și STREX, STREX, p. patru.
  35. 1 2 ARM, 2009 , 1.3.2 Implementarea unui mutex, p. 12-13.
  36. ARM, 2009 , 1.2.3 Bariere ale memoriei, p. opt.

Literatură