#1 04-01-2007 00:43:52

mieczyk

Member

3375619
Zarejestrowany: 03-08-2007
Posty: 24
Punktów :   
WWW

[C/C++]Wątki - wprowadzenie

---------------------------------------------------------------

0x01. Wstęp.
0x02. Czym jest wątek?
0x03. Wykonywanie wątków.
0x04. Tworzenie nowego wątku.
0x05. Zakończenie wątku.
0x06. Zakończenie.
0x07. Bibliografia.

---------------------------------------------------------------- 


---------------------------------------------------------------- 
0x01. Wstęp
---------------------------------------------------------------- 

Niniejszy artykuł stanowi wprowadzenie do wątków w systemie Windows. Jest również pierwszym artykułem z cyklu tekstów dotyczących wątków. Tekst ten ma charakter teoretyczny i zawiera tylko jeden przykład, mimo to,  przyda się znajomość WinAPI i C/C++. W moich tekstach dotyczących WinAPI używam darmowego środowiska programistycznego Dev-C++ 4.9.9.2. Pomimo tego, że nowością nie jest, doskonale nadaje się do nauki programowania nie tylko WinAPI. Wszystkie przykłady, które podaję powstały właśnie w Dev-C++. 

W razie błędów merytorycznych lub technicznych bardzo proszę o sprostowanie.

---------------------------------------------------------------- 
0x02. Czym jest wątek?
---------------------------------------------------------------- 

Wątek, jest to jednostka wykonawcza w obrębie jednego procesu, będąca kolejnym ciągiem instrukcji wykonywanym w obrębie tych samych danych (w tej samej przestrzeni adresowej).

Wątek składa się z dwóch podstawowych elementów: obiektu jądra (obiekt-wątek) oraz stosu wątku. Obiekt-wątek jest używany przez system operacyjny do zarządzania wątkiem. Ponadto, system przechowuje w tym obiekcie informacje statyczne o wątku. Na stosie wątku natomiast, trzymane są wszystkie parametry funkcji i zmienne lokalne potrzebne do wykonania kodu wątku.

Należy wiedzieć, że sam proces w zasadzie niczego nie wykonuje. Jest tylko "pojemnikiem" zawierającym wątki. Proces musi mieć przynajmniej jeden wątek, tzw. wątek główny, ale możliwe jest utworzenie większej liczby wątków w kontekście jednego procesu. O tym jednak, za chwilę.

Każdy wątek wykonuje kod w przestrzeni adresowej swojego procesu i operuje danymi, które należą do tej przestrzeni. Oznacza to, że jeśli uruchomimy dwa lub więcej wątków w kontekście jednego procesu, będą one korzystały z tej samej przestrzeni adresowej, a do tego mogą wykonywać ten sam kod, działać na tych samych danych. Wątki mogą także dzielić ze sobą te same uchwyty obiektów jądra, ponieważ tablice uchwytów są tworzone dla całych procesów, a nie dla poszczególnych wątków (patrz artykuł: Współużytkowanie obiektów jądra w systemie Windows).

---------------------------------------------------------------- 
0x03. Wykonywanie wątków
---------------------------------------------------------------- 

W systemach wieloprocesorowych oraz w systemach z wywłaszczeniem (czyli m.in. w "nowszych" wersjach Windows) wątki mogą być wykonywane równocześnie (współbieżnie).

Potrzebne jest jednak pewne sprostowanie. Każdy wątek w systemie ma własny zbiór rejestrów CPU i własny stos. Aby mógł działać, jednostka centralna CPU musi mu przydzielić porcję czasu zwaną kwantem. Wykonywanie wątków w systemach takich jak np. Windows XP wygląda w następujący sposób: System, według określonego algorytmu, przydziela kwant czasu CPU dla jednego z wątków. W tym krótkim momencie wątek wykonuje swój kod. Później następuje przerwanie i system przydziela kwant czasu CPU innemu wątkowi. Znów następuje wykonanie kodu, przerwanie i przydzielenie kwantu czasu CPU kolejnemu wątkowi czekającemu w kolejce. Po krótkim czasie, kwant czasu zostaje znowu przydzielony naszemu "pierwszemu" wątkowi i kontynuuje on swoje wykonanie itd. Należy jeszcze wspomnieć, że niektóre wątki mogą wywłaszczać czas wykonywania innym wątkom, mimo że ich kolejka jeszcze się nie skończyła. Dzieje się tak np. wtedy gdy te mają różne priorytety.

Sposób wykonywania wątków w systemie Windows nazywamy synchronicznym (jednoczesne wykonywanie).

---------------------------------------------------------------- 
0x04. Tworzenie nowego wątku
---------------------------------------------------------------- 

Każdy wątek musi mieć funkcję, od której ma się zaczynać jego wykonywanie. Funkcja ta nazywa się punktem wejścia. Dla głównego wątku, funkcją wejścia jest oczywiście WinMain, main, wWinMain i wmain (przedrostek 'w' oznacza wersję UNICODE).

Jeśli chcemy utworzyć w procesie wątek dodatkowy musimy napisać funkcję będącą punktem wejścia dla nowego wątku. Oto jej prototyp:


DWORD WINAPI ThreadFunction(PVOID pvParam)
{
  // Dowolny kod

  return 0;
}


W odróżnieniu od funkcji stanowiącej punkt wejścia wątku głównego, ta funkcja może mieć dowolną nazwę. Argumentem przekazywanym do tej funkcji może być dowolna wartość określona przez programistę. Do tego musi ona zwracać wartość, która będzie traktowana jako kod wyjścia wątku. Weźmy pod uwagę następujący przykład:

thread.cpp

Kod:

#include <windows.h>
#include <conio.h>
#include <iostream>

using namespace std;

DWORD WINAPI FunkcjaWatku(PVOID pvParam)
{
  cout << "Przekazana wartosc to:\t" << (int)pvParam;
 
  return 0;
}

int main()
{
   int x = 6;
   DWORD IdWatku;

 CreateThread(NULL, 0, FunkcjaWatku, (PVOID)x, 0, &IdWatku);

 getch();

 return 0;
}

•      Tym razem utworzyliśmy aplikację CUI (konsolową). Nasz przykładowy program posiada dwa wątki: główny z punktem wejścia main i jeden stworzony przez nas. Należy pamiętać, że Windows jest systemem wielowątkowym z wywłaszczeniami. Oznacza to, że te dwa wątki będą się wykonywać jednocześnie.
•      Zacznijmy od funkcji naszego wątku, czyli FunkcjaWatku. Już na piewszy rzut oka widać, że funkcja ta jest zgodna z podanym wcześniej prototypem. Nasz nowy wątek wyświetli w oknie konsoli wartość przekazaną do funkcji, czyli pvParam. Kod wyjścia tego wątku określiliśmy jako 0.
•      Przejdźmy teraz do funkcji wątku głównego, który wywoła zdefiniowany przez nas wątek potomny. Na początek definiujemy zmienną x, której wartość przekażemy do funkcji nowego wątku.
•      Teraz najważniejsza część, czyli funkcja tworząca nowy wątek (a właściwie obiekt-wątek, który jest używany przez system do zarządzania samym wątkiem). Tą funkcją jest CreateThread, a oto jej prototyp:


HANDLE CreateThread( PSECURITY_ATTRIBUTES psa,
                    DWORD cbStack,
                    PTHREAD_START_ROUTINE pfnStartAddr,
                    PVOID pvParam,
                    DWORD fdwCreate,
                    PDWORD pdwThreadId );


Pierwszy argument jest naturalnie wskaźnikiem do struktury SECURITY_ATTRIBUTES (Patrz: [ www ]). My podaliśmy NULL, co oznacza użycie standardowych atrybutów bezpieczeństwa w tworzonym obiekcie-wątku, bez możliwości dziedziczenia uchwytu. Kolejny argument, czyli cbStack określa ilość przestrzeni adresowej jaką wątek może przeznaczyć na swój stos. Jak już wspomniałem wcześniej, każdy wątek go posiada. Nie będziemy się tym teraz zajmować i podaliśmy wartość 0. Oznacza to, że stosowi wątku zostanie przydzielona cała zarezerwowana dla niego pamięć. Parametr pfnStartAddr wskazuje adres funkcji, od której ma się zacząć wykonywanie nowego wątku. My oczywiście podajemy tutaj FunkcjaWatku. pvParam określa dowolną wartość, która ma być przekazana do funkcji wątkowej. Jak widać, w naszym przykładzie zostanie przekazana wartość zmiennej x, czyli 6. Następny parametr, tj. fdwCreate przekazuje dodatkowe flagi. My podaliśmy 0 co powoduje, że wątek jest od razu zaszeregowany do wykonania. Gdybyśmy jednak podali tutaj flagę CREATE_SUSPENDED, to utworzony wątek zostałby zawieszony. Oczywiście można później wznowić wykonanie za pomocą funkcji ResumeThread( HANDLE hThread ), gdzie jako argument podajemy uchwyt obiektu-wątku. Ostatni parametr funkcji CreateThread musi przekazywać adres zmiennej typu DWORD, która będzie przechowywać identyfikator (ID) utworzonego wątku.
•      Jak widać, tworzenie wątku nie jest skomplikowaną czynnością. Ponownie przypominam o bardzo ważnej rzeczy, jaką jest synchroniczne wykonywanie wątków. Nasze dwa wątki  będą się wykonywały jednocześnie i gdyby nie było funkcji czekającej na wprowadzenie znaku getch, wątek główny mógłby się zakończyć wcześniej niż drugi zdążyłby wyświetlić napis. Istnieją jednak różne metody operowania wątkami, ale o tym w następnej części.


---------------------------------------------------------------- 
0x05. Zakończenie wątku
---------------------------------------------------------------- 

W systemie Windows istnieją cztery sposoby zakończenia działania wątku. Jednym z nich jest zwykłe wyjście z wątku, które jest najbardziej zalecane. Drugim sposobem jest wywołanie funkcji ExitThread. Jej prototyp wygląda tak:


VOID ExitThread( DWORD dwExitCode );


Ta funkcja zabija wątek, który ją wywołał, natomiast jej parametr określa kod wyjścia wątku.

Zakończyć wątek można również za pomocą funkcji TerminateThread. Prototyp wygląda tak:


BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode );


Funkcja ta różni się od ExitThread tym, że pozwala zabić dowolny wątek nie tylko ten, który ją wywołał. Myślę, że parametry funkcji są klarowne i zrozumiałe. hThread określa uchwyt obiektu-wątku, który chcemy zabić, a dwExitCode definiuje kod wyjścia zabijanego wątku.

Wątek może się również zakończyć wtedy, gdy zabijemy proces w przestrzeni którego, dany wątek działa. Tej metody należy jednak unikać. Tak naprawdę, jedynym właściwym sposobem zakończenia wątku jest zwykłe wyjście z jego funkcji. W innym przypadku mogą wystąpić problemy ze "sprzątaniem po sobie".  Czyli niektóre zasoby, z których wątek korzystał mogą nie zostać zwolnione.

---------------------------------------------------------------- 
0x06. Zakończenie
---------------------------------------------------------------- 


Ten krótki tekst był tylko wstępem do pojęcia wątków w WinAPI. Następne artykuły będą traktowały o bardziej zaawansowanych aspektach używania wątków, czyli o szeregowaniu wątków, priorytetach i synchronizacji.

Na zakończenie dodam jeszcze o możliwości stworzenia nowego wątku za pomocą funkcji _beginthreadex należącej do biblioteki czasu wykonywania C/C++. Więcj informacji na ten temat można znaleźć tutaj: [ www ]. Jeśli chcemy napisać aplikację korzystającą ogólnie ze standardowej biblioteki czasu wykonywania C/C++, to użycie funkcji _beginthreadex jest wręcz konieczne. Musimy tylko zaimportować odpowiednią bibliotekę przeznaczoną do aplikacji wielowątkowych.

---------------------------------------------------------------- 
0x07. Bibliografia
---------------------------------------------------------------- 

J. Richter, Programowanie aplikacji dla Windows, Warszawa 2002;

msdn.microsoft.com

wikipedia.org

Offline

 

Stopka forum

RSS
Powered by PunBB 1.2.23
© Copyright 2002–2008 PunBB
Polityka cookies - Wersja Lo-Fi


Darmowe Forum | Ciekawe Fora | Darmowe Fora
GotLink.pl