Synchronizacja wątków funkcjami systemu Windows

W ramach poznawania funkcji systemu Windows, synchronizacji wątków itp.. Pokażę mniej więcej na czym to polega.

Plik *.exe gdy zostanie uruchomiony jest procesem. Proces sam w sobie nic nie robi. Proces ma wątki, a ściślej ma na 100% wątek główny czyli ten który obsługuje to co wykonuje się w pliku *.exe.

Wątek może mieć wątki potomne. Watki możemy synchronizować, czyli tak manipulować czasem ich wykonanania (np. opóźniać) aby nie powstały konflikty dostępu do zasobów. Warto wspomnieć, że wątki mogą oczekiwać na pojedyńcze zgłoszenia ( WaitForSingleObject ) lub na wielokrotne ( WaitForMultipleObjects ). Zgłoszeniem w tym momencie jest ustawienie zdarzenia na zasygnalizowane („jest spoko, skończyłem!”) lub na niezasygnalizowane („ej Stary, musisz poczekać na mnie”).

Ok, nasz program będzie prosty. Niech będzie funkcja generująca wektor intów. I druga funkcja wyświetlająca ten wektor. Tablica globalna. Wiem że brzydko, ale przekazywanie w takim małym programie int* jest conajmniej nie praktyczne.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int aa [100];
void f1(void){

    double r=3;
    int i;

    for(i=0;i<100;i++){

        if(i>2){

            aa[i]=i;
        }
        else{

            aa[i]=11*r;

        }
    }

    printf(" |koniec f1| ");

}

void f2(void){

    int i;

    for(i=0;i<100;i++){

        printf("%d ", aa[i]);

    }

    printf(" |koniec f2| ");
}

int main(int argc, char *argv[ ])
{
    f1();
    f2();
    return 0;
}

No i wszystko spoko działa, nie? A co jeśli zrobię tak:

1
2
f2();
f1();

Heh, no właśnie. Wyświetli zera a potem wypełni. Przykład może wydaje się być trywialny, ale naszym zadaniem będzie tak zsynchronizować wątki, żeby mimo takiego zapisu, działały poprawnie. Zmienie prototypy funkcji i dodam inicjalizacje wątków :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
int aa [100];
HANDLE hWatekA, hWatekB;

UINT WINAPI f1(LPVOID){

    double r=3;
    int i;

    for(i=0;i<100;i++){

        if(i>2){

            aa[i]=i;
        }
        else{

            aa[i]=11*r;

        }
    }

    printf(" |koniec f1| ");

    _endthreadex(0);
}

UINT WINAPI f2(LPVOID){

    int i;

    for(i=0;i<100;i++){

        printf("%d ", aa[i]);

    }

    printf(" |koniec f2| ");
    _endthreadex(0);
}

int main(int argc, char *argv[ ])
{
    UINT ID_A=0, ID_B=0;
    hWatekA = (HANDLE)_beginthreadex(NULL, 0, f2, NULL, 0, &ID_A);
    hWatekB = (HANDLE)_beginthreadex(NULL, 0, f1, NULL, 0, &ID_B);

    printf("Po watkach..");

    return 0;
}

I co mamy teraz? 3 wątki, jeden głowny i dwa które zajmują się wywołaniami funkcji f2() i f1(). Uruchamiając to mamy niezły bałagan. Wątek głowny wjeżdza w wykonywanie się wątków potomnych, te dwa również się przeplatają. No burdel niezły. Tutaj muszę dodać zdanie wyjaśniające, jak działają wątki w SO. Każdy wątek dostaje czas procesora. I co 20ms aktywowany jest inny wątek, a tamten zatrzymywany i tak w kółko. Oczywiście do tamtego się wraca.. :) Stąd ten burdel. Ale spokojnie, można nad tym zapanować!
Pomyślmy co chcemy uzyskać. Chcemy aby wątek wywołujący f2() poczekał na wątek wywołujący f1(). A w dodatku chcemy, aby wątek głowny poczekał na oba te wątki :P
Dla wygodny uchwyty hWatekA i hWatekb przeniosłem na górę, dzięki temu stały się globalne.

1
WaitForSingleObject(HANDLE, INFINITE);

Funkcja powoduje uśpienie wątku, w którym jest wywołana do czasu, aż HANDLE nie zasygnalizuje że wszystko jest spoko i można iść dalej. Ok, my chcemy żeby czekał f2() więc utworzę globalny uchwyt do zdarzenia które będą wątki zmieniać i obserowować. Zobacz co się zmieniło:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
int aa [100];
HANDLE hWatekA, hWatekB;
HANDLE hZWatek = CreateEvent(NULL, FALSE, FALSE, NULL);
UINT WINAPI f1(LPVOID){

    double r=3;
    int i;

    for(i=0;i<100;i++){

        if(i>2){

            aa[i]=i;
        }
        else{

            aa[i]=11*r;

        }
    }

    printf(" |koniec f1| ");

    _endthreadex(0);
}

UINT WINAPI f2(LPVOID){

    int i;

    WaitForSingleObject(hZWatek, INFINITE);

    for(i=0;i<100;i++){

        printf("%d ", aa[i]);

    }

    printf(" |koniec f2| ");
    _endthreadex(0);
}

int main(int argc, char *argv[ ])
{
    UINT ID_A=0, ID_B=0;

    hWatekA = (HANDLE)_beginthreadex(NULL, 0, f2, NULL, 0, &ID_A);
    hWatekB = (HANDLE)_beginthreadex(NULL, 0, f1, NULL, 0, &ID_B);

    printf("Po watkach..");

    return 0;
}

Na górze mamy zdarzenie, na którego zasygnalizowanie czeka f2(). Zdarzenie domyślnie ustawiłem na niezasygnalizowane, więc bez zmiany, f2() nigdy się nie wykona.

1
SetEvent(hZWatek);

załatwi sprawę poinformowania f2() o tym że f1() skończyło swoją pracę (wypełniło tablicę intów).

Teraz mamy sytuację taką, że f2() wie że ma poczekać na f1(). f1() gdy skończy wypełniać tablicę informuje f2() że „wszystko jest spoko” i f2() wyświetla zawartosć tablicy. Teraz musimy się uporać z tym, aby main poczekał na te oba wątki. Tutaj już tylko wystarczt funkcja WaitForMultipleObjects(), użyta w takiej formie:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc, char *argv[ ])
{
    UINT ID_A=0, ID_B=0;

    hWatekA = (HANDLE)_beginthreadex(NULL, 0, f2, NULL, 0, &ID_A);
    hWatekB = (HANDLE)_beginthreadex(NULL, 0, f1, NULL, 0, &ID_B);

    HANDLE a[2]={hWatekA, hWatekB};

    WaitForMultipleObjects(2,a,true, INFINITE);
    printf("Po watkach..");

    CloseHandle(hMutex);
    return 0;
}

2 to ilość wątków na które czekamy, zmienna a to tablica z uchwytami. true mówi że ma poczekac do zakończenia wszystkich wątków a ostatni argument że ma czekać w nieskończoność.. :)

I tak doszliśmy do końca zadania :) Cały kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
int aa [100];
HANDLE hWatekA, hWatekB;
HANDLE hZWatek = CreateEvent(NULL, FALSE, FALSE, NULL);
UINT WINAPI f1(LPVOID){

    double r=3;
    int i;

    for(i=0;i<100;i++){

        if(i>2){

            aa[i]=i;
        }
        else{

            aa[i]=11*r;

        }
    }

    printf(" |koniec f1| ");
    SetEvent(hZWatek);

    _endthreadex(0);
}

UINT WINAPI f2(LPVOID){

    int i;

    WaitForSingleObject(hZWatek, INFINITE);

    for(i=0;i<100;i++){

        printf("%d ", aa[i]);

    }

    printf(" |koniec f2| ");
    _endthreadex(0);
}

int main(int argc, char *argv[ ])
{
    UINT ID_A=0, ID_B=0;

    hWatekA = (HANDLE)_beginthreadex(NULL, 0, f2, NULL, 0, &ID_A);
    hWatekB = (HANDLE)_beginthreadex(NULL, 0, f1, NULL, 0, &ID_B);

    HANDLE a[2]={hWatekA, hWatekB};

    WaitForMultipleObjects(2,a,true, INFINITE);
    printf("Po watkach..");

    return 0;
}

Czy to jest trudne? Hmm, takie sobie. Przykład banalny, ale dobrze obrazujący zagadnienie. Może napisze coś o mutexach… :)

PS. Przepraszam za brak include’ów – ale blog w pewnym momencie co nieco wyciął..

Dzięki za wizytę,
Mateusz Mazurek
Podziel się na:
    Facebook email PDF Wykop Twitter

Dodaj komentarz

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subskrybuj  
Powiadom o