Scrigroup - Documente si articole

     

HomeDocumenteUploadResurseAlte limbi doc
AccessAdobe photoshopAlgoritmiAutocadBaze de dateCC sharp
CalculatoareCorel drawDot netExcelFox proFrontpageHardware
HtmlInternetJavaLinuxMatlabMs dosPascal
PhpPower pointRetele calculatoareSqlTutorialsWebdesignWindows
WordXml


Fire de Executie si Mecanisme de Sincronizare in Windows

windows



+ Font mai mare | - Font mai mic



Fire de Executie si Mecanisme de Sincronizare in Windows[1]



Fiecare proces are la crearea sa un singur fir de executie si pana nu demult acest lucru parea suficient. Pentru cazurile in care un singur de fir de executie nu mai este de ajuns avem in cazul sistemelor de operare mai multe solutii.

Prima solutie este oferita de utilizarea functie:

HANDLE CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes,

SIZE_T dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter,

DWORD dwCreationFlags,

LPDWORD lpThreadId

Parametrii functiei au urmatoarea utilizare:

  • lpThreadAttributes [in[2]] - este un pointer la o structura SECURITY_ATTRIBUTES care determina daca descriptorul intors de apel poate fi mostenit de procesele copii. Daca acest parametru este NULL atunci descriptorul nu va fi mostenit.
  • dwStackSize [in] - reprezinta dimensiunea initiala a stivei. Daca acest parametru are valoarea 0 atunci noul fir de executie va utiliza o stiva avand dimensiunea implicita a executabilului.
  • lpStartAddress [in] - este un pointer la functia care va fi executata de catre fir si reprezinta adresa de start a acestuia. Nu are o valoare implicita. J
  • lpParameter [in] - pointer la o variabila care va fi transmisa firului.
  • dwCreationFlags [in] - fanioane care controleaza modul de creare al unui fir. Daca firul este creat avand specificat fanionul CREATE_SUSPENDED specificat, firul va fi creat in starea suspendat si nu va rula pana la apelul functiei ResumeThread. Daca aceasta valoare este 0 atunci firul va fi rulat imediat dupa creare.
  • lpThreadId [out] - este un pointer la o variabila in care va fi memorat identificatorul firului, cu conditia ca acest parametru sa fie diferit de NULL. In Windows Me/98/95 acest parametru nu poate fi NULL.

In caz de esec functia intoarce NULL, altfel va intoarce descriptorul firului. In MSDN Library puteti gasi urmatorul exemplu de utilizare a functiei:

#include <windows.h>

#include <conio.h>

DWORD WINAPI ThreadFunc( LPVOID lpParam )

void main( void )

else

Firul principal transmite valoarea 1 noului fir, care o afiseaza. Terminarea unui fir nu presupune si stergerea obiectului asociat firului de catre sistemul de operare. Un obiect thread este sters atunci cand si ultimul descriptor (HANDLE) catre fir este inchis, acest lucru realizandu-l apelul:

CloseHandle( hThread );

In cazul in care doriti sa folositi functii din bibliotecile C statice, utilizarea CreateThread nu este prea indicata deoarece duce la mici pierderi de memorie la terminarea fiecarui fir. In acest caz este recomandata utilizarea celei de-a doua metode, oferita de functiile:

uintptr_t _beginthread(

void( __cdecl *start_address )( void * ),

unsigned stack_size,

void *arglist

uintptr_t _beginthreadex(

void *security,

unsigned stack_size,

unsigned ( __stdcall *start_address )( void * ),

void *arglist,

unsigned initflag,

unsigned *thrdaddr

Parametrii:

  • start_address - adresa de inceput a functiei cu care incepe executia noului fir.
  • stack_size - dimensiunea stivei pe care o va folosi noul fir. Daca acest parametru este 0 atunci va fi folosita aceeasi dimensiune ca in cazul firului principal.
  • arglist - un pointer la zona de memorie care contine variabilele care trebuie transmise firului sau NULL.
  • security - pointer la o structura SECURITY_ATTRIBUTES care determina daca descriptorul intors de apel poate fi mostenit de procesele copii. Daca este NULL atunci descriptorul nu va fi mostenit. In Windows 95 acest parametru trebuie sa fie NULL.
  • initflag - Starea initiala a noului fir, CREATE_SUSPENDED daca doriti ca firul sa fie creat in starea suspendat, caz in care trebuie rulata functia ResumeThread, sau 0 pentru ca firul sa fie rulat chiar de la creare.
  • thrdaddr - pointer la o variabila pe 32 de biti in care va fi memorat identificatorul firului.

In caz de succes ambele functii intorc un descriptor pentru noul fir creat. In caz de eroare _beginthread intoarce -1L, iar cealalta functie intoarce 0.

Atunci cand un fir se termina se apeleaza automat _endthread, respectiv _endthreadex. Apelul endthread inchide automat descriptorul asociat cu firul, lucru pe care nu-l face _endthreadex.

Atunci cand doriti sa compilati un program care foloseste aceste functii va trebui sa folositi o biblioteca multifir. O cale prin care puteti realiza acest lucru este data de modificarea setarii 'Use run-time library'[3] astfel incat sa contina Multithreaded in nume.

#include <stdio.h>

#include <process.h>

#include <windows.h>

volatile running;

void fir(void* numar);

void main()

while(running);

void fir( void* numar)

Exemplul de mai sus are mai multe bug-uri, unul dintre ele fiind folosirea abuziva a calificatorului volatile. Obiectele declarate ca volatile nu sunt folosite in optimizari de catre compilator deoarece valoarea lor poate fi modificata de catre altcineva. Sistemul va citi de fiecare data valoarea curenta a obiectului volatil chiar daca aceasta se gasea deja intr-un registru si valoarea volatila va fi scrisa imediat dupa asignare. Dar nimeni nu garanteaza ca operatiile sunt atomice. Iata cum arata incrementarea valorii running in asamblare:

mov  eax,[running (0042d3f0)]

add  eax,1

mov  [running (0042d3f0)],eax

Acest modificator este potrivit numai pentru instructiuni in care variabila respectiva este numai folosita sau numai atribuita.

Un alt bug, si nu singurul, continut de exemplul anterior este dat de faptul ca foloseste asteptarea continua pentru sincronizare. Din fericire, in Windows avem o mare varietate de mecanisme de sincronizare. In continuare, o sa fie prezentate doar trei dintre ele: evenimente, mutexuri si semafoare.

Evenimente, Mutex-uri si Semafoare

Un obiect eveniment poate avea doar doua stari: semnalizat sau nesemnalizat. Este util in cazul in care doriti sa transmiteti unui fir un anumit semnal care sa-i indice ca un anumit eveniment a avut loc, avand avantajul fata de variabilele conditie ca pierderea respectivului semnal este mult mai greu de obtinut printr-o programare defectuoasaJ

Crearea unui obiect eveniment se face cu ajutorul functiei[4]:

HANDLE CreateEvent(

LPSECURITY_ATTRIBUTES lpEventAttributes,

BOOL bManualReset,

BOOL bInitialState,

LPCTSTR lpName

  • lpEventAttributes - reprezinta un pointer la o structura SECURITY_ATTRIBUTES in functie de care se stabileste daca descriptorul intors poate fi mostenit de un proces copil. Daca acest parametru este NULL, atunci el nu va fi mostenit.
  • bManualReset - daca acest parametru este TRUE atunci evenimentul este resetat manual, aceasta insemnand ca trebuie folosita functia ResetEvent. Daca acest parametru este FALSE atunci evenimentul este resetat automat la eliberarea unui singur fir care astepta ca respectivul eveniment sa devina semnalizat.[5]
  • bInitialState - reprezinta starea initiala: TRUE = semnalizat; FALSE = nesemnalizat.
  • lpName - un pointer la un string care specifica numele pe care il va avea evenimentul sau NULL daca doriti sa nu aiba un nume. Daca exista un alt obiect cu acelasi nume de alt tip apelul esueaza. Daca exista evenimentul cu acest nume, creat de un alt fir, functia necesita dreptul de acces EVENT_ALL_ACCESS. Comparatia numelor este facuta fireste tinandu-se cont de tipul caracterelor.

In caz de succes functia intoarce un descriptor pentru eveniment. In caz de esec intoarce NULL, iar daca evenimentul este deja creat intoarce descriptorul respectivului eveniment si GetLastError va intoarce ERROR_ALREADY_EXISTS.

Pentru a obtine un descriptor pentru un eveniment deja creat de un alt fir dintr-un alt proces, avem functia:s

HANDLE OpenEvent(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

LPCTSTR lpName

  • dwDesiredAccess - specifica modul de acces la eveniment[6]. Apelul esueaza daca accesul cerut nu este permis.
  • bInheritHandle - daca este TRUE atunci poate fi mostenit, altfel nu.
  • lpName - numele evenimentului.

In caz de succes functia intoarce un descriptor al evenimentului, altfel intoarce NULL.

Pentru a semnaliza un obiect se foloseste functia SetEvent:

BOOL SetEvent(

HANDLE hEvent

  • hEvent - descriptorul evenimentului, care trebuie sa aiba setat dreptul EVENT_MODIFY_STATE.

Functia intoarce in caz de succes o valoare diferita de zero sau zero in caz de esec.

Daca evenimentul este resetabil manual, din cand in cand este poate util sa-i schimbati starea in nesemnalizat, folosind functia ResetEvent:

BOOL ResetEvent(

HANDLE hEvent

  • hEvent - descriptorul evenimentului, care trebuie sa aiba setat dreptul EVENT_MODIFY_STATE.

Functia intoarce in caz de succes o valoare diferita de zero sau zero in caz de esec.

Pentru a astepta unul sau mai multe evenimente se vor folosi functiile de asteptare.

Pentru a crea un obiect mutex avem functia:

HANDLE CreateMutex(

LPSECURITY_ATTRIBUTES lpMutexAttributes,

BOOL bInitialOwner,

LPCTSTR lpName

  • lpMutexAttributes - pointer la o structura SECURITY_ATTRIBUTES care specifica daca descriptorul poate fi mostenit. Daca este NULL atunci descriptorul nu poate fi mostenit.
  • bInitialOwner - daca acest parametru este TRUE si firul apelant este cel care a creat mutexul atunci firul apelant devine proprietarul mutexului. Altfel, nu.
  • lpName - numele mutexului. Daca exista un mutex cu acelasi nume atunci functia va cere MUTEX_ALL_ACCESS. Daca exista un alt tip de obiect cu acelasi nume atunci functia esueaza.

In caz de succes functia intoarce un descriptor pentru mutex. In caz de esec intoarce NULL, iar daca mutexul este deja creat intoarce descriptorul respectivului mutex si GetLastError va intoarce ERROR_ALREADY_EXISTS.

Pentru a obtine un descriptor pentru un mutex deja creat de un alt fir dintr-un alt proces, avem functia:s

HANDLE OpenMutex(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

LPCTSTR lpName

  • dwDesiredAccess - specifica modul de acces la mutex[7]. Apelul esueaza daca accesul cerut nu este permis.
  • bInheritHandle - daca este TRUE atunci poate fi mostenit, altfel nu.
  • lpName - numele mutexului.

In caz de succes functia intoarce un descriptor al mutexului, altfel intoarce NULL.

Pentru a bloca un mutex se foloseste una din functiile de asteptare.

Pentru a elibera un mutex:

BOOL ReleaseMutex(

HANDLE hMutex

Functia intoarce in caz de succes o valoare diferita de zero sau zero in caz de esec.

Pentru a crea sau pentru a obtine un descriptor pentru un semafor avem functiile:

HANDLE CreateSemaphore(

LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,

LONG lInitialCount,

LONG lMaximumCount,

LPCTSTR lpName

HANDLE OpenSemaphore(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

LPCTSTR lpName

Parametrii au semnificatie asemanatoare cu cei ai functiilor pentru evenimente si mutexuri. Apar in plus doi parametrii care specifica valoarea initiala a semaforului si valoarea maxima pe care o poate avea acesta.

Pentru a creste contorul unui semafor cu o anumita valoare avem functia ReleaseSemaphore:

BOOL ReleaseSemaphore(

HANDLE hSemaphore,

LONG lReleaseCount,

LPLONG lpPreviousCount

  • hSemaphore - descriptorul semaforului.
  • lReleaseCount - valoarea cu care va fi crescut contorul semaforului. Valoarea trebuie sa fie mai mare decat zero si contorul nu trebuie sa creasca mai mult decat valoarea maxima.In caz contrar functia intoarce FALSE.
  • lpPreviousCount - un pointer la o variabila in care va fi salvata valoarea anterioara a contorului. Poate fi NULL.

Intoarce zero in caz de esec si o valoare diferita de zero altfel.

Functii de Asteptare

In aceasta sectiune vor fi prezentate doar cateva din functiile de asteptare.

Pentru a astepta dupa un singur obiect, avem:

DWORD WaitForSingleObject(

HANDLE hHandle,

DWORD dwMilliseconds

  • hHandle - un descriptor al obiectului dupa care se asteapta. Daca descriptorul este inchis in timpul asteptarii, comportamentul functiei nu este specificat.
  • dwMilliseconds - intervalul de timp in care se asteapta. Daca este zero, functia testeaza starea obiectului si intoarce imediat. Daca este INFINITE atunci poate astepta la nesfarsit.

Daca functia reuseste, valoarea intoarsa va indica ce eveniment a facut ca functia sa termine asteptarea. Poate avea una din valorile:

  • WAIT_ABANDONED - Firul proprietar al obiectului specificat si-a terminat executia fara a-l elibera. Firul apelant devine proprietar al obiectului, starea acestuia devenind nesemnalizat.
  • WAIT_OBJECT_0 - starea obiectului este semnalizat.
  • WAIT_TIMEOUT - a expirat intervalul de timp specificat si starea obiectului este nesemnalizat.

In caz de esec intoarce WAIT_FAILED.

Functia blocheaza firul apelant pana cand expira intervalul specificat sau pana cand obiectul specificat devine semnalizat.

Pentru a astepta mai multe obiecte:

DWORD WaitForMultipleObjects(

DWORD nCount,

const HANDLE* lpHandles,

BOOL bWaitAll,

DWORD dwMilliseconds

  • nCount - numarul de descriptori din vectorul lpHandles. Numarul trebuise sa fie mai mic sau egal cu MAXIMUM_WAIT_OBJECTS.
  • lpHandles - adresa de inceput a unui vector de descriptori. Nu poate contine un descriptor de mai multe ori. Daca un descriptor este inchis in timpul asteptarii, comportamentul este nespecificat. Pentru Windows Me/98/95: nici un descriptor nu trebuie sa fi fost obtinut cu ajutorul DuplicateHandle.
  • bWaitAll - daca acest parametru este TRUE se asteapta ca starea tuturor obiectelor sa devina semnalizat. Altfel, se asteapta dupa oricare dintre obiecte.
  • dwMilliseconds - intervalul care se asteapta. Zero si INFINITE au aceeasi semnificatie ca la asteptarea dupa un singur obiect.

In caz de succes, valoarea intoarsa va indica ce anume a cauzat terminarea asteptarii:

  • WAIT_OBJECT_0 la (WAIT_OBJECT_0 + nCount - 1) - daca bWaitAll este TRUE, valoarea indica faptul ca toate obiectele au fost semnalizate. Daca bWaitAll este FALSE, valoarea intoarsa minus WAIT_OBJECT_0 indica indexul cel mai mic al unui obiect care a incheiat asteptarea.
  • WAIT_ABANDONED_0 la (WAIT_ABANDONED_0 + nCount - 1) - Daca bWaitAll este TRUE, valoarea indica faptul ca toate obiectele sunt fie semnalizate sau abandonate, cel putin unul fiind abandonat. Daca bWaitAll este FALSE, valoarea intoarsa minus WAIT_ABANDONED_0 indica indexul obiectului care a finalizat asteptarea.
  • WAIT_TIMEOUT - A expirat timpul fara ca cealalta conditie de finalizare a asteptarii sa fie satisfacuta.

In caz de esec, functia intoarce WAIT_FAILED.

Exemplu de folosire a evenimentelor, mutex-urilor si a functiilor de asteptare:

#include <stdio.h>

#include <process.h>

#include <memory.h>

#include <malloc.h>

#include <stdlib.h>

#include <windows.h>

HANDLE evast[4];

void aduna(void* args);

void* puneArgumente(int v[], int nrElemente, int fir, int nrFire)

int suma = 0;

HANDLE hMutex1, hMutex2;

void main();

int n = 9;

// Create a mutex with no initial owner.

hMutex1 = CreateMutex(

NULL, // no security attributes

FALSE, // initially not owned

'mutex pentru printf'); // name of mutex

if (hMutex1 == NULL)

hMutex2 = CreateMutex(

NULL, // no security attributes

FALSE, // initially not owned

'mutex pentru suma'); // name of mutex

if (hMutex2 == NULL)

int nrFire = 3;

for( int i=0; i< nrFire; i++)

_beginthread(aduna, 0, args);

}

WaitForMultipleObjects(

nrFire, // number of objects in array

evast, // array of objects

TRUE, // wait for all

INFINITE);

printf('suma este %dn', suma);

void aduna(void* args)

Probleme Propuse.

  1. Sa se determine daca un element apare intr-un vector oarecare, folosind n procesoare.
  2. Sa se determine pe ce pozitii apare un element intr-un vector oarecare, folosind n procesoare.
  3. O resursa poate fi utilizata de doua tipuri de procese: albe si negre. Atunci cand resursa este folosita de procese albe ea nu mai poate fi folosita de procese negre. Este valabila si reciproca. Sa se implementeze accesul la resursa, evitandu-se infometarea.
  4. Sa se sorteze elementele unui vector cu n procesoare.
  5. Sa se genereze toate numerele prime mai mici decat o anumita valoare, folosind n procesoare.
  6. Sa se coordoneze semafoarele de la capatul unui tunel cu o singura banda, folosindu-se de senzorii de la fiecare capete care semnalizeaza intrarea si iesirea masinilor, astfel incat sa nu se permita intrarea pe la un capat cat timp mai sunt masini in tunel mergand in sens invers. Masinile sa fie simulate cu ajutorul firelor de executie.


In afara cazului in care este specificat altfel, termenul 'Windows' se refera la toate sistemele de operare Microsoft de la Windows 95 pana la Windows XP.

Parametru de intrare.

Project->Settings sau Alt+F7 dupa care la rubrica C/C++ se selecteaza din combo-ul Category valoarea Code Generation.

Toate functiile descrise in continuare necesita includerea 'Windows.h' si biblioteca Kerenel32.lib.

Adica semnalizarea duce la eliberarea numai a unui singur fir, indiferent cate asteapta.

Consultati 'Synchronization Object Security and Access Rights' din MSDN pentru detalii.

Consultati 'Synchronization Object Security and Access Rights' din MSDN pentru detalii.



Politica de confidentialitate | Termeni si conditii de utilizare



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 1494
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved