1.2.1 Pipe-uri:
Asa numitele pipe-uri permit ca doua procese distincte sa comunice intre ele. Ele sunt numite si FIFO-uri si pot fi folosite pentru stabilirea unei comunicatii unidirectionale (half-duplex)
Pipe-urile sunt identificate prin punctul lor de acces , adica un fisier din sistemul de fisiere. Deoarece pipe-urile cu nume au calea unui fisier asociat cu ele , este posibil ca doua procese neasociate sa comunice intre ele; Cu alte cuvinte , doua procese neinrudite pot deschide fisierul asociat pipe-urilor si sa initieze comunicatia. Spre deosebire de pipe-urile anonime , care sunt procese ce insista pe obiecte , pipe-urile denumite sunt obiecte persistente in sistem, si exista chiar dupa terminarea procesului. Acestea trebuie sterse explicit de catre un proces , prin apelul de “unlink” sau sterse din sistemul de fisiere prin linia de comanda.
Pentru a comunica prin pipe-uri denumite , procesele trebuie sa incarce fisierul asociat cu pipe-ul respectiv. Prin deschiderea fisierului respectiv pentru a fi citit , un proces are acces prin citirea capatului pipe-ului si prin deschiderea fisierului de scris , procesul avand acces la capatul de scriere a pipe-ului.
Un pipe numit suporta operatii de blocare a scrierii si citirii : De exemplu , daca un proces deschide un fisier pentru citire , acesta este blocat de un alt proces care deschide fisierul pentru scriere, si invers. Insa , este posibil sa creem pipe-uri numite care suporta ne-blocarea operatiilor prin specificarea fanionului O_NONBLOCK in timpul deschiderii lor. Un pipe numit trebuie deschis in modul scriere sau citire exclusiva. El nu poate fi deschis in modul scriere/citire simultana datorita faptului ca este half-duplex, adica un canal unidirectional.
Exemplu de creare a unui pipe din linia de comanda:
% mknod npipe p
sau
% mkfifo npipe
De asemenea se pot specifica caile absolute ale pipe-ului numit , ce trebuie creat.
Daca se observa fisierul cu ajutorul comenzii ls ?| vom primi urmatorul raspuns:
prw-rw-r-- 1 secf other 0 Jun 6 17:35 npipe
Caracterul p din prima coloana denota faptul ca este un pipe denumit. La fel ca in orice sistem de fisiere , acesta are permisiuni care definesc modul in care utilizatorii pot accesa un pipe denumit, daca pot scrie , citi sau ambele .
Citirea si scrierea intr-un Pipe:
Citirea sau scrierea intr-un pipe este foarte similara cu citirea sau scrierea intr-un fisier normal. Functiile standard ale librariilor C read() si write() pot fi folosite pentru scrierea si citirea dintr-un pipe.
Beneficii ale utilizarii Pipe-urilor denumite:
-
sunt foarte usor de utilizat.
-
mkfifo este o functie nedaunatoare firelor de executie
-
nu avem nevoie de un mecanism de sincronizare pentru pipe-uri denumite
-
scrierea intr-un pipe este foarte precisa, chiar si atunci cand pipe-ul este deschis in starea fara blocare.
-
pipe-urile numite au permisiuni de scriere si citire asociate , spre deosebire de pipe-urile anonime. Aceste permisiuni pot fi impuse pentru comunicatii securizate.
Limitatii ale pipe-urilor denumite:
-
pipe-urile denumite pot fi folosite doar pentru comunicatia intre procese de pe acelasi calculator
-
pipe-urile denumite pot fi create doar de catre sistemul de fisiere local, si nu se pot crea pe sistemul de fisiere NFS.
-
datorita naturii lor de blocare , este necesara de o programare riguroasa pentru client si server , in vederea evitarii incurcaturilor.
-
pipe-urile denumite este un flux de octeti , insa nu au nici un identificator.
1.2.2 Semnale:
Semnalele sunt un mod de trimitere a mesajelor simple catre procese. Majoritatea acestor procese au fost definite deja . Insa , semnalele pot fi procesate atunci cand procesul este in modul utilizator.
Daca un semnal este trimis unui proces aflat in modul kernel , acesta este tratat dupa revenirea la modul de utilizator.
Semnalele sunt cea mai veche metoda de comunicare intre procese folosita de sistemele UNIX. Acestea sunt folosite pentru a semnala evenimente asincrone unui sau mai multor procese . Un semnal poate fi generat de o intrerupere de la tastatura cum ar fi o conditie de eroare cauzata de un proces ce face referire la un spatiu de memorie inexistent in memoria sa virtuala. Semnalele sunt folosite de catre sistemele de operare pentru a semnaliza comenzile de control catre descendentii proceselor.
Exista un set fix de semnale definite pe care kernelul le poate genera sau care pot fi generate de catre alte procese din sistem , daca au privilegiile necesare.La o listare a acestor semnale cu ajutorul comenzii kill -l puteti observa :
1)SIGHUP 2) SIGINT 3) SIGQUIT
-
SIGILL 5) SIGTRA 6) SIGIOT
-
SIGBUS 8) SIGFPE 9) SIGKILL
-
SIGUSR1 11) SIGSEGV 12) SIGUSR2
-
SIGPIPE 14) SIGALRM 15) SIGTERM
-
SIGCHLD 18) SIGCONT 19) SIGSTOP
-
SIGTSTP 21) SIGTTIN 22) SIGTTOU
-
SIGURG 24) SIGXCPU 25) SIGXFSZ
-
SIGVTALRM 27) SIGPROF 28) SIGWINCH
-
29) SIGIO 30) SIGPWR
Procesele pot alege sa ignore majoritatea semnalelor generate , cu doua exceptii importante: nici SigStop care obliga procesul sa se opreasca sau Sigkill care obliga semnalul sa termine , nu pot fi ignorate. In caz contrar un proces poate alege cum doreste sa interpreteze diferite semnale. Procesele pot bloca diferite semnale , si daca nu le blocheaza pot alege sa le trateze ele insile sau kernelul. Daca kernelul trateaza aceste semnale , el va adopta actiunea generica cand un proces primeste Sigfpe va initializa o copie de siguranta si va iesi. Semnalele nu au prioritati relative . Daca doua semnale sunt generate pentru un proces in acelasi timp , atunci el le poate trata in orice ordine. De asemenea , nu exista nici un mecanisc de distinctie prin care procesul sa isi dea seama daca daca a primit unu sau mai multe semnale de acelasi fel.
Semnalele nu sunt prezentate procesului imediat dupa generare , in schimb ele trebuie sa astepte pana cand procesul va rula inca o data . De fiecare data cand un proces termina printr-un apel de sistem , campurile de signal si blocked sunt verificate , si daca exista semnale deblocate , ele pot fi acum transferate. Aceasta metoda poate parea ca una ineficienta deoarece ea depinde de faptul ca procesele verifica semnalele , insa fiecare proces din sistem face apeluri la sistem , de exemplu pentru a scrie un caracter in terminal , tot timpul. Procesele pot alege sa astepte semnale daca vor , si sunt suspendate intr-o stare neintreruptibila pana semnalul dorit soseste. Procesorul de semnale din linux studiaza structura sigaction pentru fiecare dintre semnalele deblocate.
Multe semnale ( la fel ca semnalul 9 SIGKILL ) au posibilitatea de terminare imediata a procesului, insa multe din aceste semnale pot fi ignorate sau tratate de catre proces.Daca nu se intampla asa , kernelul va lua masura implicita pentru acel semnal. Utilizatorul poate trimite semnale proceselor prin comanda kill, sau ctrl+alt+delete in Windows. Insa , el poate trimite semnale proceselor pe care le detine, in cazul in care nu are drepturi depline.
Este posibil ca procesul caruia i se trimite semnalul sa fie in stare latenta , iar daca el este intr-o stare cu prioritate intreruptibila , el este trezit la viata pentru a reactiona la semnal.
Kernelul urmareste toate semnalele in asteptare intr-o structura de procese.Aceasta este o valoare de 32 de biti in care fiecare bit reprezinta un semnal singular.Deoarece exista doar un singur bit/semnal , pot exista doar cate un semnal din fiecare fel. Daca exista mai multe semnale de tipuri diferite in asteptare , kernelul nu poate stabili ordinea de sosire a lor , astfel prioritatea de procesare a semnalelor este crescatoare corespunzator numarului semnalului.
Vutan Gheorghe Adrian (433 A)
1.3 Mecanisme de sincronizare
Introducere
Pentru sincronizarea firelor de execuție avem la dispoziție:
-
secțiuni critice (excludere mutuală în cadrul aceluiași proces) – doar Win32
-
mutex: POSIX, Win32
-
semafoare: POSIX, Win32
-
variabile de condiție: POSIX, Win32 (începând cu Vista)
-
evenimente: doar Win32
-
timere: doar Win32.
Standardul POSIX specifică funcții de sincronizare pentru fiecare tip de obiect de sincronizare. API-ul Win32, fiind controlat de o singură entitate, permite ca toate obiectele de sincronizare să poată fi utilizate cu funcțiile standard de sincronizare: WaitForSingleObject, WaitForMultipleObjects sau SignalObjectAndWait.
Mecanismele de sincronizare oferite de sistemul de operare Windows sunt mai multe şi mai complexe decât cele din Linux. Pentru sincronizare sunt necesare:
-
unul sau mai multe obiecte de sincronizare (Synchronization Objects)
-
o funcţie de aşteptare (Wait Functions).
1.3.1 Semafoare POSIX
Semafoarele sunt resurse IPC folosite pentru sincronizarea între procese (e.g. pentru controlul accesului la resurse). Operaţiile asupra unui semafor pot fi de setare sau verificare a valorii (care poate fi mai mare sau egala cu 0) sau de test and set. Un semafor poate fi privit ca un contor ce poate fi incrementat şi decrementat, dar a cărui valoare nu poate scădea sub 0.
Semafoarele POSIX sunt de 2 tipuri:
-
cu nume, folosite în general pentru sincronizare intre procese distincte;
-
fără nume (bazate pe memorie), ce pot fi folosite doar pentru sincronizarea între firele de execuţie ale unui proces.
În continuare vor fi luate în discuție semafoarele cu nume. Diferențele față de cele bazată pe memorie constă în funcțiile de creare și distrugere, celelalte funcții fiind identice.
ambele tipuri de semafoare sunt reprezentate în cod prin tipul sem_t.
semafoarele cu nume sunt idenficate la nivel de sistem printr-un şir de forma ”/nume”.
fişierele antet necesare sunt , şi .
Operațiile care pot fi efectuate asupra semafoarelor POSIX sunt:
/* SEMAFOARE CU NUME */
// deschiderea unui semafor identificat prin nume.
// folosit pentru a sincroniza procese diferite
sem_t* sem_open(const char *name, int oflag);
sem_t* sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
// închiderea unui semafor cu nume
int sem_close(sem_t *sem);
// stergerea din sistem a unui semafor cu nume
int sem_unlink(const char *name);
/* SEMAFOARE FARA NUME */
/** initializarea unui semafor fara nume
* sem - semaforul nou creat
* pshared - 0 daca semaforul nu este partajat decat
de firele de executie ale procesului curent
- non zero: semafor partajat cu alte procese
in acest caz 'sem' trebuie alocat intr-o zona
de memorie partajata
* value - valoarea initiala a semaforului
*/
int sem_init(sem_t *sem, int pshared, unsigned int value);
// distrugerea unui semafor fara nume
int sem_destroy(sem_t *sem);
/* OPERATII PE SEMAFOARE */
// incrementarea (V)
int sem_post(sem_t *sem);
// decrementarea blocantă (P)
int sem_wait(sem_t *sem);
// decrementarea neblocantă
int sem_trywait(sem_t *sem);
// citirea valorii
int sem_getvalue(sem_t *sem, int *pvalue);
Operaţii:
-
-
down decrementează un semafor doar dacă valoarea lui este strict mai mare decât 0 dacă semaforul are valoarea 0, atunci procesul se blochează (intră în sleep)
-
up incrementează valoarea unui semafor
dacă unul sau mai multe procese sunt blocate pe respectivul semafor (nu au putut continua o operaţie down anterioară pe semafor cu valoarea 0), se va debloca unul dintre ele, pentru a-şi încheia operaţia down operaţia up nu poate bloca un proces
Operaţiile iniţiate asupra unui semafor sunt indivizibile
Problema producător-consumator rezolvată cu ajutorul a trei semafoare:
-
-
semaforul full numără poziţiile ocupate din buffer
-
semaforul empty contorizează poziţiile libere din buffer
-
semaforul mutex ce împiedică accesul simultan la buffer (excluderea mutuală)
Iniţial: full = 0, empty = N (dimensiunea bufferului) şi mutex=1
a) empty = 0; full = N; mutex = 1 şi se execută producer( )
b) empty = N; full = 0; mutex = 1 şi se execută consumer( )
c) empty != 0; full != 0; mutex = 1 şi cele două procese încearcă să intre simultan în SC
-
full nu permite consumatorului să extragă informaţii din buffer dacă acesta este gol,
-
empty nu permite producătorului să introducă informaţii în buffer dacă el este plin, iar
-
mutex nu permite celor două procese să acceseze simultan bufferul (excludere mutuală)
#define N 100 /* number of slots in the buffer*/
typedef int semaphore; /* semaphores are a special kind of int */
semaphore mutex = 1; /* controls access to critical region */
semaphore empty = N; /* counts empty buffer slots */
semaphore full = 0; /* counts full buffer slots */
void producer(void)
{ int item;
while (TRUE) { /* infinite loop */
item = produce_item(); /* generate something to put in buffer */
down(&empty); /* decrement empty count */
down(&mutex); /* enter critical region */
insert_item(item); /* put new item in buffer – Crit. Sect.*/
up(&mutex); /* leave critical region */
up(&full); /* increment count of full slots */
} }
void consumer(void)
{ int item;
while (TRUE) { /* infinite loop */
down(&full); /* decrement full count */
down(&mutex); /* enter critical region */
item = remove_item(); /* take item from buffer – Crit. Sect.*/
up(&mutex); /* leave critical region */
up(&empty); /* increment count of empty slots */
consume_item(item); /* do something with the item */
} }
Procesul producer se va bloca pe instrucţiunea down(&empty) deoarece empty = 0.
Deblocarea procesului va avea loc abia după ce procesul consumer va executa instrucţiunea up(&empty).
Semafoare(Win32)
Un semafor (semaphore) este un obiect de sincronizare care are intern un contor ce ia doar valori pozitive. Atât timp cât semaforul (contorul) are valori strict pozitive el este considerat disponibil (signaled). Când valoarea semaforului a ajuns la zero el devine indisponibil (nonsignaled) şi următoarea încercare de decrementare va duce la o blocare a threadului de pe care s-a făcut apelul (şi a procesului, dacă acesta foloseşte un singur thread) până când semaforul devine disponibil.
Operaţia de decrementare se realizează doar cu o singură unitate (la fel ca în API-ul POSIX, dar spre deosebire de API-ul SysV unde se poate face decrementarea atomică a unui semafor cu mai multe unităţi o dată), în timp ce incrementarea se poate realiza cu orice valoare în limita maximă.
Crearea şi deschiderea
Funcţia de creare a semafoarelor este CreateSemaphore şi are sintaxa :
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCTSTR lpNAME
);
Se observă că funcţia se poate folosi şi pentru deschiderea unui semafor deja existent.
Alternativ, pentru a folosi un semafor deja existent este necesară obţinerea HANDLE-ului semaforului, operaţie ce se realizează folosind funcţia OpenSemaphore cu următoarea sintaxă :
HANDLE OpenSemaphore(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpNAME
);
Decrementarea/aşteptarea şi incrementarea
Operaţia de decrementare a semaforului cu sau fără aşteptare se realizează folosind una din funcţiile de aşteptare. Cea mai des folosită este funcţia WaitForSingleObject.
Incrementarea semaforului se realizează folosind funcţia ReleaseSemaphore cu sintaxa :
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount
);
Distrugerea
Operaţia de distrugere a unui semafor este similară cu cea de distrugere a unui mutex. Se foloseşte funcţia CloseHandle. După ce toate HANDLE-urile unui semafor au fost închise, semaforul este distrus şi resursele ocupate de acesta eliberate.
1.3.2 Mutex-uri
Un mutex este un obiect de sincronizare care poate fi deţinut (posedat, acaparat) doar de un singur proces (sau thread) la un moment dat. Drept urmare, operaţiile de baza cu mutex-uri sunt cele de obţinere şi de eliberare.
Odatǎ obţinut de un proces, un mutex devine indisponibil pentru orice alt proces. Orice proces care încearcǎ sǎ acapareze un mutex indisponibil, se va bloca (un timp definit sau nu) aşteptând ca el sǎ devinǎ disponibil.
Mutex-urile sunt cel mai des folosite pentru a permite unui singur proces la un moment dat sǎ acceseze o resursǎ.
În continuare vor fi prezentate operaţiile cu mutex-uri.
Crearea/deschiderea sunt operaţii prin care se pregǎteşte un mutex. Dupǎ cum am mai spus, pentru a opera cu orice obiect de sincronizare este necesar un HANDLE al acelui obiect. Scopul funcţiei de creare şi a celei de deschidere este acela de a obţine un HANDLE al obiectului mutex. Prin urmare, este necesar doar un singur apel, fie el de creare sau de deschidere (se presupune ca alt proces a creat deja mutex-ul). Acest apel este efectuat o singura datǎ la iniţializare; odatǎ ce avem HANDLE-ul putem obţine/elibera mutex-ul ori de câte ori avem nevoie.
Pentru a crea un mutex se foloseşte funcţia CreateMutex cu sintaxa :
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
Pentru a deschide un mutex deja existent este definită funcţia OpenMutex cu sintaxa :
HANDLE OpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);
Încercarea de acaparare a unui mutex presupune următorii paşi :
-
verificarea daca mutex-ul este disponibil?
-
daca da, îl pot acapara şi devine indisponibil, şi funcţia întoarce succes
-
daca nu, aştept să devină disponibil, după care îl acaparez, şi funcţia întoarce succes
-
la time-out funcţia întoarce eroare (atenţie! e posibil să nu existe time-out)
Încercarea de obţinere se poate face cu sau fară timp de expirare (time-out) în funcţie de parametrii daţi funcţiilor de aşteptare. Cea mai des folosită funcţie de aşteptare este WaitForSingleObject.
Folosind funcţia ReleaseMutex se cedează posesia mutex-ului, el devenind iar disponibil. Funcţia are următoarea sintaxa :
BOOL ReleaseMutex(
HANDLE hMutex
);
Funcţia va eşua dacă procesul nu deţine mutex-ul.
Atenţie pentru a putea folosi această funcţie HANDLE-ul trebuie să aibă dreptul de acces MUTEX_MODIFY_STATE.
Operaţia de distrugere a unui mutex este aceeaşi ca pentru orice HANDLE. Se foloseşte funcţia CloseHandle. După ce toate HANDLE-urile unui mutex au fost închise, mutexul este distrus şi resursele ocupate de acesta eliberate.
Atenţie La terminarea execuţiei unui program toate HANDLE-urile folosite de acesta sunt automat închise. Deci spre deosebire de semafoarele IPC din Linux, este imposibil ca un mutex (sau semafor) în Windows să mai existe în sistem după ce programele care l-au folosit/creat s-au terminat.
1.3.3 Evenimente
Evenimentele sunt obiecte de sincronizare al caror statut poate fi explicitat prin utilizarea functiei SetEvent.
Exista 2 tipuri de evenimente:
-
Cu resetare manuala: Un obiect eveniment a carui stare ramane “semnalizat” pana cand este resetat in mod explicit ca nesemnalizat prin utilizarea functiei ResetEvent. Deşi este semnalat, orice număr de fire de aşteptare, sau fire care specifică ulterior obiectul aceluiaşi eveniment într-una din funcţiile de aşteptare , acestea pot fi eliberate.
-
Cu resetare automata: Un obiect eveniment a cărei stare rămâne “semnalat” pana cand un singur fir de aşteptare este eliberat, moment în care sistemul seteaza automat starea acestuia nesemnalat. Dacă nu sunt fire de aşteptare, starea obiectului eveniment rămâne semnalat. Dacă mai mult de un fir este în aşteptare, un fir de aşteptare este selectat. Nu presupune un primul-intrat, primul-ieşit (FIFO). Evenimentele externe, cum ar fi TAB-uri de kernel-mode pot schimba ordinea aşteptarii.
Obiectul Eveniment este util în a trimite un semnal pe un fir care să indice că un anumit eveniment a avut loc. De exemplu, în suprapunerea de intrare şi de ieşire, sistemul stabileşte un anumit obiect eveniment la starea semnalat atunci când operaţiunea de suprapunere a fost finalizata. Un singur fir poate specificaobiecte eveniment diferite în mai multe operaţiuni simultane de suprapunere, apoi utilizaţi una dintre funcţiile de aşteptare pentru ca starea unuia dintre obiectele eveniment să fie semnalate.
Un fir utilizează CreateEvent sau CreateEventExpentru a crea un obiect eveniment. Firul creat specifică starea iniţială a obiectului şi dacă acesta este un obiecteveniment cu resetare manuala sau un obiect eveniment auto-resetare.
1.3.4 Secțiune critică
În Windows mai există un mecanism de sincronizare care este disponibil doar pentru firele de execuție ale aceluiași proces, și anume CriticalSection. Se recomandă folosirea CriticalSection pentru excluderea mutuală a firelor de execuție ale aceluiași proces, fiind mai eficient decât Mutex sau Semaphore.
Obiectele CriticalSection sunt echivalente mutexurilor POSIX de tip RECURSIVE. Obiectele CriticalSection sunt folosite pentru excluderea mutuală a accesului firelor de execuție ale aceluiași proces la o secțiune critică de cod care conține operații asupra unor date partajate. Un singur fir de execuție va fi activ la un moment dat în interiorul secțiunii critice, și dacă mai multe fire așteaptă să intre, nu este garantată ordinea lor de intrare, totuși sistemul va fi echitabil față de toate.
Operațiile care se pot efectua asupra unei secțiuni critice sunt: intrarea, intrarea neblocantă, ieșirea din secțiunea critică, inițializarea și distrugerea.
Pentru serializarea accesului la o secțiune de cod critică, fiecare fir de execuție va trebui să intre într-un obiect CriticalSection la începutul secțiunii și să-l părăsească la sfârșitul ei. În acest fel, dacă două fire de execuție încearcă să intre în CriticalSection simultan, doar unul dintre ele va reuși, și își va continua execuția în interiorul secțiunii critice, iar celălalt se va bloca pînă când obiectul CriticalSection va fi părăsit de primul fir. Așadar, la sfârșitul secțiunii, primul fir trebuie să părăsească obiectul CriticalSection, permițându-i celuilalt intrarea.
Pentru excluderea mutuală se pot folosi atât obiecte Mutex, cât și obiecte CriticalSection; dacă sincronizarea trebuie făcută doar între firele de execuție ale aceluiași proces este recomandată folosirea CriticalSection, fiind mai un mecanism mai eficient. Operația de intrare în CriticalSection se traduce într-o singură instrucțiune de asamblare de tip test-and-set-lock (TSL). CriticalSection este echivalentul futex-ului din Linux.
Inițializarea/distrugerea unei secțiuni critice
Alocarea memoriei pentru o secțiune critică se face prin declararea unui obiect CRITICAL_SECTION. Acesta nu va putea fi folosit, totuși, înainte de a fi inițializat.
// initializează o secțiune critică cu un contor de spin implicit = 0
void InitializeCriticalSection(LPCRITICAL_SECTION pcrit_sect);
// permite specificarea contorului de spin
BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION pcrit_sect, DWORD dwSpinCount);
// modifică contorul de spin al unei secțiuni critice
DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION pcrit_sect, DWORD dwSpinCount);
// distrugerea unei secțiuni critice
void DeleteCriticalSection(LPCRITICAL_SECTION pcrit_sect);
Un obiect CRITICAL_SECTION nu poate fi copiat ori modificat după inițializare. De asemenea, un obiect CRITICAL_SECTION nu trebuie inițializat de două ori, în caz contrar, comportamentul său fiind nedefinit.
Contorul de spin are sens doar pe sistemele multiprocesor (SMP) (este ignorat pe sisteme uniprocesor). Contorul de spin reprezintă numărul de cicli pe care îi petrece un fir de execuție pe un procesor în busy-waiting, înainte de a-și suspenda execuția la un semafor asociat secțiunii critice, în așteptarea eliberării acesteia. Scopul așteptării unui număr de cicli în busy-waiting este evitarea blocării la semafor în cazul în care secțiunea critică se eliberează în intervalul respectiv, deoarece blocarea la semafor are impact asupra performanțelor. Folosirea contorului de spin este recomandată mai ales în cazul unei secțiuni critice scurte, accesate foarte des.
Utilizarea secțiunilor critice
Secțiunile critice Windows au comportamentul mutex-urilor POSIX de tip RECURSIVE. Un fir de execuție care se află deja în secțiunea critică nu se va bloca dacă apelează din nou EnterCriticalSection, însă va trebui să părăsească secțiunea critică de un număr de ori egal cu cel al ocupărilor, pentru a o elibera.
// similar cu pthread_mutex_lock() pentru mutexuri RECURSIVE
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
// similar cu pthread_mutex_unlock() pentru mutexuri RECURSIVE
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
// pentru a folosi TryEnterCriticalSection trebuie definit
// _WIN32_WINNT >= 0x0400 înainte de a include prima dată
#define _WIN32_WINNT 0x0400
#include
// similar cu pthread_mutex_trylock() pentru mutexuri RECURSIVE
// - întoarce FALSE (=0) dacă secțiunea critică este ocupată de alt
// fir de execuție și NU blochează firul curent de execuție
// - întoarce o valoarea nenulă dacă secțiunea critică era liberă
// sau ocupată tot de acest fir de execuție
BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
În cadrul unui fir de execuție, numărul apelurilor LeaveCriticalSection trebuie să fie egal cu numărul apelurilor EnterCriticalSection, pentru a elibera în final secțiunea critică. Dacă un fir de execuție care nu a intrat în secțiunea critică apelează LeaveCriticalSection, se va produce o eroare care va face ca firele care au apelat EnterCriticalSection să aștepte pentru o perioadă nedefinită de timp.
Dostları ilə paylaş: |