Table of Contents

1. Obiecte de sincronizare
1.1. Mutex-uri
1.1.1. Crearea sau Deschiderea
1.1.2. Posesia
1.1.3. Eliberarea
1.1.4. Un mic exemplu
1.2. Semafoare
1.2.1. Desciderea
1.2.2. Incrementarea semaforului
1.2.3. Distrugerea semaforului
1.3. Observatii
2. Functii de asteptare
2.1. Functii de asteptare dupa un singur obiect
2.2. Functii de asteptare dupa mai multe obiecte
2.3. Functii de asteptare alertabile
2.4. Functii de asteptare inregistrate
 
 

  
 

 Sistemul de operare Windows pune la dispozitie o serie de mecanisme de comunicare si schimb de date intre aplicatii. Cazul de care ne vom ocupa este doar cel in care aceste aplicatii sunt procese care ruleaza pe aceeasi masina.
Inainte de a fi prezentate mecanismele de comunicare in sine trebuie introduse mecanismele de sincronizare, care sunt folosite pentru controlul accesului la resurse.
Mecanismele de sincronizare oferite de sistemul de operare Windows sunt mai multe si mai complexe decat cele din Linux. Pentru sincronizare sunt necesare unul sau mai multe obiecte de sincronizare (synchronization objects) folosite impreuna cu o functie de asteptare (wait function).
Atentie! Obiectele de sincronizare nu pot fi folosite fara functii de sincronizare.
Odata parcurse mecanismele de sincronizare, vor fi prezentate doar doua din mecanisme de comunicare puse la dispozitie de Windows: File Mapping (memorie/fisiere partajate) si Mail Slots (cozi de mesaje).



1. Obiecte de sincronizare


Obiectele de sincronizare (synchronization objects) sunt gestionate global de catre sistem, si sunt reprezentate in programe prin tipul HANDLE. Acest HANDLE nu este altceva decat un identificator, similar cu descriptorul de fisier. Orice operatie asupra unui obiect de sincronizare va implica cel putin un parametru de tip HANDLE, prin acesta specificandu-se obiectul asupra caruia functia are efect.
Un obiectele de sincronizare este caracterizat (in mod general) de:
  • tip ( event, mutex, semaphore, wait timer, process, etc)
  • nume, care poate fi inexistent
  • stare (disponibil = 'signaled' sau indisponibil = 'nonsignaled')

Starea unui obiect de sincronizare poate fi disponibil ('signaled') sau indisponibil 'nonsignaled' si poate fi modificata de operatiile cu respectivul obiect si de functiile de asteptare aplicate lui; acestea din urma, la randul, lor fiind afectate de starea obiectului.
Dupa tip, obiectele de sincronizare se impart in:
  • obiecte folosite exclusiv pentru sincronizare
    Sunt obiecte al caror unic scop este sincronizarea. In aceasta categorie intra:

    • evenimentele (Events)
    • mutex-urile
    • semafoarele (semaphores)
    • waitable timer
  • obiecte care pe langa functia de baza pot fi folosite si pentru sincronizare
    Sunt obiecte care au alta functionalitate primara, dar ofera si posibilitatea de a fi folosite pentru sincronizare. Din aceasta categorie fac parte (printre altele):

    • Intrarea de consola (console input)
    • Procesele
    • Thread-urile

1.1. Mutex-uri


Un Mutex este un obiect de sincronizare care poate fi detinut (posedat, acaparat) doar de un singur proces (sau thread) la un moment dat. Drept urmare operatiile de baza cu mutex-uri sunt cele de posedare si eliberare.
Odata posedat de un proces, un mutex devine indisponibil pentru orice alt proces. Orice proces care incearca sa posede un mutex indisponibil, se va bloca (un timp definit sau nu) asteptand ca el sa fie disponibil.
Mutex-urile sunt cel mai des folosite pentru a permite la un moment dat unui singur proces sa acceseze o resursa.
Pentru a intelege mai usor ce este un mutex si cum este folosit el, putem asimila notiunea de mutex cu cea a unei chei de la o cutie, iar procesele cu persoane. Persoana care detine cheia (detine mutex-ul), este singura care poate accesa cutia. In cazul in care persoana doreste sa renunte la cheie este nevoie de un dispecer care sa pastreze cheia pana la venirea urmatoarei persoane care doreste cheia. Acest dispecer este implementat chiar in sistemul de operare.
In continuare vor fi prezentate operatiile cu mutex-uri.
1.1.1. Crearea sau Deschiderea

Crearea/deschiderea sunt operatii prin care se pregateste un mutex. Dupa cum am spus mai sus, pentru a opera cu orice obiect de sincronizare este necesar un HANDLE al acelui obiect. Scopul functiei de creare si a celei de deschidere este acela de a obtine 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 data la initializare; odata ce avem HANDLE-ul putem poseda/elibera mutex-ul de cate ori avem nevoie.
Pentru a crea un mutex se foloseste functia CreateMutex() cu sintaxa:

HANDLE CreateMutex(

LPSECURITY_ATTRIBUTES lpMutexAttributes,

BOOL bInitialOwner,

LPCTSTR lpName

);

Parametrii avand urmatoarea semnificatie:
  • lpMutexAttributes
    Pointer la o structura SECURITY_ATTRIBUTES care determina daca HANDLE-ul returnat poate fi mostenit de precesele copil. Daca este NULL, se considera ca nu poate fi mostenit.
    Mai multe informatii despre aceasta structura se gasesc in MSDN.
  • bInitialOwner
    Daca este TRUE procesul care creeaza mutex-ul il va detine automat. Daca este FALSE, mutex-ul creat va fi disponibil
  • lpName
    Pointer la un sir de caractere care specifica numele mutex-ului. Atentie, este case-sensitive!
    Daca este NULL, obiectul mutex creat va fi anonim.
    Daca exista deja un mutex cu numele specificat, se face o cerere de deschidere cu drepturile MUTEX_ALL_ACCCESS. In acest caz bInitialOwner este ignorat.
    Daca exista deja un obiect eveniment, semafor, waitable timer, job sau file-mapping cu numele specificat, functia va intoarce eroare.

Se observa ca se poate folosi functia de creare a unui mutex pe post de functie de deschidere a unui mutex deja existent.

Pentru a deschide un mutex deja existent este definita functia OpenMutex() cu sintaxa:

HANDLE OpenMutex(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

LPCTSTR lpName

);

Parametri au urmatoarea semnificatie:
  • dwDesiredAccess
    Modul de acces dorit. Functia esueaza daca accesul dorit nu este permis de descriptorul de securitate al obiectului.
  • bInhertHandle
    TRUE daca se doreste ca procesele copil sa mosteneasca acest handle
  • lpNAME
    Numele semaforului; case-sensitive.

1.1.2. Posesia

Posesia unui mutex se realizeaza folosind functiile de asteptare care sunt tratate intr-o sectiune urmatoare.
Incercarea de posedare a unui mutex presupune urmatorii pasi:
  1. este mutex-ul disponibil?
  2. daca da, il pot acapara si devine indisponibil, si functia intoarce succes
  3. daca nu, astept sa devina disponibil, dupa care il acaparez, si functia intoarce succes
  4. time-out, si functia intoarce eroare (atentie! e posibil sa nu existe time-out)

Incercarea de posedare se poate face cu sau fara timp de expirare (time-out) in functie de parametrii dati functiilor de asteptare.
Cea mai des folosita functie de asteptare este WaitForSingleObject() .

1.1.3. Eliberarea

Folosind functia ReleaseMutex() se cedeaza posesia mutex-ului, el devenind iar disponibil. Functia are urmatoarea sintaxa:

BOOL ReleaseMutex(

HANDLE hMutex

);

Singurul parametru reprezinta handle-ul mutex-ului care se doreste eliberat.
Functia va esua daca procesul nu detine mutex-ul.
Atentie! pentru a putea folosi aceasta functie handle-ul trebuie sa aiba dreptul de acces MUTEX_MODIFY_STATE.

1.1.4. Un mic exemplu

Urmatorul exemplu creeaza un mutex care va fi in starea disponibil:

HANDLE hMutex; 

hMutex = CreateMutex(

NULL, // fara atribute de securitate

FALSE, // mutexul va fi disponibil

"MutexToProtectAVariable"); // numele mutex-ului



if (hMutex == NULL)

DoError("Nu pot crea mutex-ul");

Functia DoError() nu este prezenta, ea avand rolul de a trata eroarea respectiva, de obicei afisarea unui mesaj, eventual eliberarea altor resurse si terminarea programului.


1.2. Semafoare


Un Semafoar (semaphore) este un fel de mutex mai avansat, el reprezentand un fel de contor ce ia doar valori pozitive. Ideea este ca atata timp cat semaforul (contorul) are valori strict pozitive el este considerat disponibil (signaled). Cand valoarea semaforului a ajuns la zero el devine indisponibil (unsignaled) si urmatoarea incercare de decrementare va duce la o blocare a procesului in cauza pana cand semaforul devine disponibil.
Operatiile de intrare in posesie si eliberare de la mutex-uri sunt inlocuite la semafoare cu operatii de decrementare si respectiv incrementare.
Operatia de decrementare se realizeaza doar cu o singura unitate, in timp ce incrementarea se poate realiza cu orice valoare in limita maxima.
Observatie! Un mutex poate fi simulat cu un semafor de valoare maxima 1. Crearea
Functia de creare a semafoarelor este CreateSemaphore() si are sintaxa:

HANDLE CreateSemaphore(

LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,

LONG lInitialCount,

LONG lMaximumCount,

LPCTSTR lpNAME

);

Parametrii reprezinta:
  • lpSemaphoreAttributes
    Pointer la o structura SECURITY_ATTRIBUTES care determina daca HANDLE-ul returnat poate fi mostenit de precesele copil. Daca este NULL, se considera ca nu poate fi mostenit.
  • lInitialCount
    Valoarea initiala a semaforului. Poate sa fie chiar zero.
  • lMaximumCount
    Valoarea maxima a semaforului. Trebuie sa fie strict pozitiva.
  • lpName
    Pointer la un sir de caractere care specifica numele semaforului. Poate avea dimensiunea maxima MAX_PATH si este case-sensitive!
    Daca este NULL, obiectul semafor creat va fi anonim.
    Daca exista deja un semafor cu numele specificat, se face o cerere de deschidere cu drepturile SEMAPHORE_ALL_ACCCESS. In acest caz lInitialCount si lMaximumCount sunt ignorate, deoarece ele au fost deja setate cand semaforul a fost creat.
    Daca exista deja un event, mutex, waitable timer, job sau file-mapping cu numele specificat, functia va intoarce eroare.

Se observa ca functia CreateSemaphore() se poate folosi si pentu deschiderea unui semafor deja existent.
1.2.1. Desciderea

Pentru a folosi un semafor deja existent este necesara obtinerea HANDLE-ului semaforului. Operatie ce se realizeaza folosind functia OpenSemaphore() cu urmatoarea sintaxa:

HANDLE OpenSemaphore(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

LPCTSTR lpNAME

);

Parametrii:
  • dwDesiredAccess
    Modul de access dorit. Functia esueaza daca accessul dorit nu este permis de descriptorul de securitate al obiectului.
  • bInhertHandle
    TRUE daca se doreste ca procesele copil sa mosteneasca acest handle.
  • lpNAME
    Numele semaforului; case-sensitive.
Decrementarea semaforului / asteptarea la semafor
Operatia de decrementare a semaforului cu sau fara asteptare se realizeaza folosind una din functiile de asteptare. Cea mai des folosita fiind WaitForSingleObject() .

1.2.2. Incrementarea semaforului

Incrementarea semaforului se realizeaza folosind functia ReleaseSemaphore() cu sintaxa:

BOOL ReleaseSemaphore(

HANDLE hSemaphore,

LONG lReleaseCount,

LPLONG lpReviousCount

);

Semnificatia parametrilor:
  • hSemaphore
    HANDLE-ul reprezentand semaforul asupra caruia se executa operatia
  • lReleaseCount
    Valoarea cu care se incrementeaza semaforul; trebuie sa fie strict pozitiva. Daca valoarea in urma incrementarii semaforul ar depasi valoarea maxima el NU este incrementat si functia esueaza.
  • lpPreviousCount
    Pointer unde va fi intoarsa valoarea semaforului inainte de a fi incrementat. Poate fi NULL daca nu ne intereseaza aceasta valoare.

1.2.3. Distrugerea semaforului

Operatia de distrugere a unui semafor este aceeasi ca pentru orice HANDLE. Se foloseste functia CloseHandle(). Dupa ce toate HANDLE-urile unui semafor au fost inchise semaforul este distrus si resursele ocupate de acesta eliberate.
Observatie: La terminarea execuitei unui program toate HANDLE-urile folosite de acesta sunt automat inchise. Deci spre deosebire de semafoarele IPC din Linux, este imposibil ca un semafor in Windows sa mai existe in sistem dupa ce programele care l-au folosit/creat s-au terminat.


1.3. Observatii

  • Numele obiectelor de sincronizare (parametrul LPCTSTR lpName ) nu poate contine caracterul '\' (backslash), pentru sistemele de operare Windows 95/98/Me/NT
  • Numele obiectelor de sincronizare (parametrul LPCTSTR lpName ) poate contine prefixul "Global\" sau "Local\" pentru a crea explicit un obiect in spatiul de nume global sau sesiune. Folosit doar in cazul in care Terminal Services ruleaza.

2. Functii de asteptare


Functiile de asteptare (wait functions) au rolul de a bloca executia unui proces/thread pana la indeplinirea unor conditii. Tipul functiei de asteptare si parametrii acesteia determina aceste conditii. Pana cand conditiile sunt indeplinite procesul/threadul intra in stare de asteptare.

Exista patru tipuri de functii de asteptare in functie de numarul de obiecte de sincronizare folosite si de comportament:
  • single-object
  • multiple-object
  • alertable
  • registered

2.1. Functii de asteptare dupa un singur obiect


Aceste functii asteapta dupa un singur obiect de sincronizare. Executia lor se termina cand una din urmatoarele conditii este adevarata:
  • Obiectul de sincronizare este in starea 'signaled'
  • Timpul de asteptare (time-out) a expirat. Acest timp poate fi setat ca INFINITE pentru a specifica ca timpul de asteptare nu va expira niciodata

Rezultatul intors de aceste functii poate fi:
  • WAIT_OBJECT_0
    Succes
  • WAIT_ABANDONED
    Obiectul specificat este un mutex care a fost abandonat, adica thread-ul care-l detinea s-a terminat fara sa-l elibereze.
  • WAIT_IO_COMPLETION
    Asteptarea a fost intrerupta de un apel asincron de procedura.
  • WAIT_TIMEOUT
    Timpul de expirare s-a scurs.
  • WAIT_FAILED
    Functia a esuat. Informatii despre eroare pot fi obtinute folosind functia GetLastError() .

In continuare sunt prezentare pe larg functiile care fac parte din aceasta categorie: SignalObjectAndWait()
Aceasta functie semnalizeaza un obiect si asteapta dupa altul.
Functia are sintaxa:

DWRORD SignalObjectAndWait(

HANDLE hObjectToSignal,

HANDLE hObjectToWaitOn,

DWORD dwMilliseconds,

BOOL bAlertable

);

Parametrii au umatoarea semnificatie:
  • hObjectToSignal
    Obiectul care va fi semnalat (semafor, mutex sau eveniment)
  • hObjectToWaitOn
    Obiectul dupa care se asteapta sa fie liber (semafor, mutex, event, waitable timer, proces, thread, etc)
  • dwMilliseconds
    Timpul dupa care expira, in milisecunde. Poate fi INFINITE.
  • bAlertable
    Daca este TRUE, functia se termina cand sistemul programeaza o rutina de tratare unei operatii de I/O terminata sau a unui apel asincron terminat.
WaitForSingleObject()
Functia asteapta dupa un singur obiect si are sintaxa:

DWORD WaitForSingleObject(

HANDLE hHandle,

DWORD dwMilliseconds

);

Parametrii:
  • hHandle - handle-ul obiectului dupa care se asteapta
  • dwMilliseconds - timpul de asteptare. Poate fi INFINITE

Valorile returnate pot fi doar: WAIT_ABANDONED, WAIT_OBJECT_0, WAIT_TIMEOUT si bine inteles WAIT_FAILED. WaitForSingleObjectEx()
Asteapta dupa un singur obiect si are sintaxa:

DWORD WaitForSingleObjectEx(

HANDLE hHandle,

DWORD dwMilliseconds,

BOOL bAlertable

);

Parametrii au semnificatiile similare celor doua functii deja prezentate Un mic exemplu
Exemplu de cod care asteapta la un seamfor:

DWORD dwWaitResult; 

dwWaitResult = WaitForSingleObject(

hSemaphore, // HANDLE-ul unui semafor

100); // timpul de asteptare



switch (dwWaitResult)

{

case WAIT_OBJECT_0:

// OK, putem face ceea ce avem de facut

break;

case WAIT_TIMEOUT:

// Timpul a expirat si nu am trecut de semafor.

break;

case WAIT_FAILED:

// am esuat

DoError("nu am putut astepta la semafor");

break;

}

HANDLE-ul hSemaphore se presupune a fi handle-ul valid al unui semafor.
Functia DoError() nu este prezenta, ea avand rolul de a trata eroarea respectiva, de obicei afisarea unui mesaj, eventual eliberarea altor resurse si terminarea programului.

2.2. Functii de asteptare dupa mai multe obiecte


Aceste functii asteapta dupa mai multe obiecte de sincronizare. Executia lor se termina cand una din urmatoarele conditii este adevarata:
  • Starea unui obiect de sincronizare SAU starea tuturor obiectelor de sincronizare este 'signaled' (depinde de parametrii)
  • Timpul de asteptare (time-out) a expirat. Acest timp poate fi setat ca INFINITE pentru a specifica ca timpul de asteptare nu va expira niciodata

In continuare sunt prezentare pe larg functiile care fac parte din aceasta categorie: WaitForMultipleObjects()
Functia asteapta dupa mai multe obiecte si are sintaxa:

DWORD WaitForMultipleObjects(

DWORD nCount,

const HANDLE* lpHandles,

BOOL bWaitAll,

DWORD dwMilliseconds

);

Parametrii avand urmatoarea semnificatie:
  • nCount - Numarul de obiecte
  • lpHandles
    Pointer la un vector de HANDLE-uri ale obiectelor de asteptare.
    Lista poate contine tipuri de obiecte diferite, dar nu poate contine un obiect de doua ori.
  • bWaitAll
    Daca este TRUE se asteapta ca TOATE obiectele sa fie in starea 'signaled'. Daca este FALSE este de ajuns ca un singur obiect sa fie in starea 'signaled'
  • dwMilliseconds - timpul de asteptare

Valorile posibile pe care le intoarce aceasta functie sunt:
  • WAIT_OBJECT_0 ...pana la... (WAIT_OBJECT_0 + nCount - 1)
    Daca bWaitAll este TRUE, valoarea returnata indica faptul ca starea tuturor obiectelor este 'signaled'.
    Daca bWaitAll este FALSE, valoarea returnata minus WAIT_OBJECT_0 indica indexul obiectului (din vector) care a cauzat terminarea asteptarii. In cazul in care mai multe obiecte au cauzat terminarea asteptarii, valoarea in cauza este valoarea celui cu indexul cel mai mic.
  • WAIT_ABANDONED_0 ...pana la... (WAIT_ABANDONED_0 + nCount - 1)
    Daca bWaitAll este TRUE, valoarea returnata indica faptul ca starea tuturor obiectelor este 'signaled', dar cel putin unul din ele este un mutex abandonat.
    Daca bWaitAll este FALSE, valoarea returnata minus WAIT_ABANDONED_0 indica indexul obiectului care a cauzat terminarea asteptarii si care este un mutex abandonat.
  • WAIT_TIMEOUT
    timpul de asteptare a expirat
WaitForMultipleObjectsEx()
Asteapta dupa mai multe obiecte si are sintaxa:

DWORD WaitForMultipleObjectsEx(

DWORD nCount,

const HANDLE* lpHandles,

BOOL bWaitAll,

DWORD dwMilliseconds,

BOOL bAlertable

);

Parametrii au semnificatia similara cu cei de la functia WaitForMultipleObjects(), iar bAlertable are aceeasi semnificatie ca la functia WaitForSingleObjectEx().
Valorile pe care le intoarce aceasta functie sunt cele de la functia WaitForMultipleObjects() si WAIT_IO_COMPLETION, cu semnificatiile corespunzatoare.

2.3. Functii de asteptare alertabile


Functiile care se incadreaza in aceasta categorie sunt:
  • WaitForSingleObjectEx()
  • WaitForMultipleObjectsEx()
  • SignalObjectAndWait()

Aceste functii ofera posibilitatea de a efectua operatii de asteptare alertabile. O operatie de asteptare alertabila se poate termina cand:
  • conditiile specificate sunt adevarate
  • sistemul programeaza o rutina de tratare a operatiilor de I/O terminate
  • sistemul programeaza o rutina de tratare a unui apel asincron terminat

Controlul alertabilitatii se realizeaza prin parametrul BOOL bAlertable pe care aceste functii il accepta.

2.4. Functii de asteptare inregistrate


Aceste functii sunt folosite de prgramele care folosesc mai multe thread-uri.
 
 

Citeste si partea a doua ...

Inapoi la pagina principala