Kategorie: Programowanie

Synchronizacja wątków funkcjami systemu Windows

Cześć! Cieszę się, że mnie odwiedziłeś/aś. Zanim przejdziesz do artykułu chciałbym zwrocić Ci uwagę na to, że ten artykuł był pisany kilka lat temu (2012-12-27) miej więc proszę na uwadzę że rozwiązania i przemyślenia które tu znajdziesz nie muszą być aktualne. Niemniej jednak zachęcam do przeczytania.

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
Mateusz M.

Ostatnie wpisy

Podsumowanie: luty i marzec 2024

Ostatnio tygodnie były tak bardzo wypełnione, że nie udało mi się napisać nawet krótkiego podsumowanie. Więc dziś zbiorczo podsumuję luty… Read More

2 tygodnie ago

Podsumowanie: styczeń 2024

Zapraszam na krótkie podsumowanie miesiąca. Książki W styczniu przeczytałem "Homo Deus: Historia jutra". Książka łudząco podoba do wcześniejszej książki tego… Read More

3 miesiące ago

Podsumowanie roku 2023

Cześć! Zapraszam na podsumowanie roku 2023. Książki Zacznijmy od książek. W tym roku cel 35 książek nie został osiągnięty. Niemniej… Read More

3 miesiące ago

Podsumowanie: grudzień 2023

Zapraszam na krótkie podsumowanie miesiąca. Książki W grudniu skończyłem czytać Mein Kampf. Nudna książka. Ciekawsze fragmenty można by było streścić… Read More

4 miesiące ago

Praca zdalna – co z nią dalej?

Cześć, ostatnio w Internecie pojawiło się dużo artykułów, które nie były przychylne pracy zdalnej. Z drugiej strony większość komentarzy pod… Read More

4 miesiące ago

Podsumowanie: listopad 2023

Zapraszam na krótkie podsumowanie miesiąca. Książki W listopadzie dokończyłem cykl "Z mgły zrodzony" Sandersona. Tylko "Stop prawa" mi nie do… Read More

5 miesięcy ago