Programowanie współbieżne i rozproszone Procesy współbieżne
Zawartość Procesy i wątki: Procesy współbieżne Synchroniczne i asynchroniczne operacje I/O Możliwości i ograniczenia; operacje I/O i potrzeba synchronizacji, Tworzenie aplikacji współbieżnych w systemie Windows Tworzenie potoku funkcja CreateNamedPipe, Nawiązywanie połączenia przez potok funkcje ConnectNamedPipe, DisconnectNamedPipe, CreateFile Operacje I/O funkcje WriteFile, ReadFile, PeekNamedPipe
Sekwencyjność a współbieżność Wykonanie sekwencyjne Poszczególne akcje procesu są wykonywane jedna po drugiej kolejna akcja rozpoczyna się po całkowitym zakończeniu poprzedniej. Wykonanie współbieżne Kolejna akcja rozpoczyna się przed zakończeniem poprzedniej. Akcje te są wykonywane równolegle lub w przeplocie.
Równoległość a współbieżność Wykonanie równoległe Kilka akcji jest wykonywanych w tym samym czasie. Jest to "prawdziwa" współbieżność, możliwa do uzyskania na komputerze z wieloma procesorami lub wielu komputerach. Wykonanie w przeplocie Jednocześnie odbywa się wykonanie tylko jednej akcji, jednak jest wiele czynności rozpoczętych i wykonywanych na zmianę krótkimi fragmentami. Wykonanie współbieżne jest abstrakcją równoległości i zawiera w sobie zarówno wykonania równoległe jak i wykonania w przeplocie
Wykonanie równoległe procesy czas Procesy wykonywane równolegle (tyle procesów, co procesorów/rdzeni)
Wykonanie w przeplocie wykonywanie czas gotowość/wstrzymanie Procesy wykonywane w przeplocie (mogą mieć różny priorytet na przydział czasu procesora)
Procesy współbiezne Środowisko wielozadaniowe (Unix, Linux, Windows XP/Vista) może współbieżnie wykonywać wiele procesów. Dopóki procesy te nie współzależą od siebie, każdy może być traktowany jak wykonywany sekwencyjnie.
Procesy współbieżne procesy czas Procesy wykonywane współbieżnie (a) Procesy niezależne od siebie
Procesy współbiezne Środowisko wielozadaniowe (Unix, Linux, Windows XP/Vista) może współbieżnie wykonywać wiele procesów. Dopóki procesy te nie współzależą od siebie, każdy może być traktowany jak wykonywany sekwencyjnie. Procesy współbieżne mogą wpływać na wykonanie innych procesów lub od nich w pewien sposób zależeć. Do zarządzania pracą procesów współbieżnych system operacyjny udostępnia szereg mechanizmów: Synchronizacja (Mutex, Semaphore, CriticalSection) Sygnalizacja (Event, Message) Komunikacja (Pipe, Shared Memory)
Procesy współbieżne procesy Sync I/O Req Sync I/O Compl Sync czas Procesy wykonywane współbieżnie (b) Procesy komunikują się ze sobą i są zależne od siebie nawzajem
Procesy współbiezne Komunikacja między procesami może odbywać się przy wykorzystaniu dwóch mechanizmów: Potok (ang. Pipe) rodzaj strumienia I/O, ustanawianego między procesami (zamiast, jak zwykle, między procesem a urządzeniem I/O); Potok łączy tylko dwa procesy i nie ma możliwości zmiany danych uprzednio do niego wpisanych Współdzielona pamięć (ang. Shared Memory) Obszar pamięci o dostępie swobodnym, współdzielony pomiędzy wieloma procesami; w praktyce plik wymiany Niezależnie od metody, komunikacja między procesami odbywa się według reguł operacji I/O i wymaga sprawnej sygnalizacji oraz synchronizacji.
Programy współbieżne Możliwości i ograniczenia: Odrębna przestrzeń adresowa wymiana danych między procesami: przez operacje I/O, przy współudziale systemu operacyjnego Wykonanie współbieżne (równoległe lub w przeplocie) konieczność synchronizacji, zwłaszcza operacji I/O
Operacje I/O Operacje I/O mogą być wykonywane w trzech różnych trybach: Synchronicznie z blokowaniem Proces zgłasza do systemu żądanie operacji I/O z własnej inicjatywy i zostaje wstrzymany aż do zakończenia operacji I/O Synchronicznie bez blokowania Proces zgłasza do systemu żądanie operacji I/O z własnej inicjatywy. Operacja I/O zostaje zakończona natychmiast, nawet jeżeli nie kończy się powodzeniem w takim przypadku proces musi sam zadbać o zgłoszenie żądania za jakiś czas. Asynchronicznie Proces zgłasza do systemu żądanie operacji I/O. Operacja I/O nie jest wykonywana natychmiast a proces nie jest wstrzymywany. Kiedy system zakończy operację I/O, poinformuje o tym proces, który zgłaszał żądanie, sposobem określonym przy zgłaszaniu żądania
Operacje I/O procesy OS I/O przekazanie sterowania czas Operacja I/O synchroniczna z blokowaniem: Wstrzymanie procesu do zakończenia operacji I/O.
Operacje I/O procesy OS I/O przekazanie sterowania czas Operacja I/O synchroniczna bez blokowania: Zakończenie operacji I/O, z powodzeniem lub bez.
Operacje I/O procesy OS I/O I/O Req I/O Compl przekazanie sterowania czas Operacja I/O asynchroniczna: System operacyjny powiadamia o zakończeniu I/O.
Programy wielowątkowe Zastosowanie: Bardziej efektywne wykorzystanie możliwości systemu systemy rastrowe, wieloprocesorowe i wielordzeniowe Wykonywanie niektórych czynności w czasie rzeczywistym tzw. systemy osadzone, obsługa multimediów itd. Zapobieganie blokowaniu głównego wątku w czasie operacji wejścia/wyjścia dysk, COM/LPT/USB, sieć itp. Zapobieganie blokowaniu wątku z GUI podczas czasochłonnych operacji kodowanie AV, nagrywanie CD/DVD itp. Wykonywanie mniej pilnych czynności w tle, kiedy główny wątek programu jest mniej obciążony - np. indeksowanie, drukowanie itp.
Program jednowątkowy procesy/wątki czas Proces jest wykonywany sekwencyjnie (może być równolegle z innymi procesami)
Program wielowątkowy procesy/wątki czas Proces jest podzielony na wątki, wykonywane współbieżnie (może być równolegle z innymi procesami)
Programy współbieżne/wielowątkowe procesy/wątki czas Wykonanie blokujących operacji I/O przez wydzielony wątek
Pipe Do obsługi potoków dedykowane są funkcje: CreateNamedPipe, CreatePipe WaitNamedPipe, ConnectNamedPipe, PeekNamedPipe, DisconnectNamedPipe Ponadto można wykorzystywać: WriteFile, ReadFile, WaitForSingleObject, WaitForMultipleObjects CloseHandle GetLastError
CreatePipe Funkcja CreatePipe tworzy anonimowy potok, który można wykorzystać w obrębie jednego procesu (wielowątkowego): BOOL CreatePipe( PHANDLE hreadpipe, PHANDLE hwritepipe, LPSECURITY_ATTRIBUTES lppipeattributes, DWORD nsize ); Argumenty: hreadpipe, hwritepipe wskaźniki do zmiennych, które otrzymają uchwyty do obu końców potoku (odczyt/zapis) lpthreadattributes wskaźnik do struktury atrybutów nsize rozmiar bufora (0 domyślny)
CreateNamedPipe Funkcja CreateNamedPipe tworzy nazwany potok, który można wykorzystać do komunikacji między procesami: HANDLE CreateNamedPipe( LPCTSTR lpname, DWORD dwopenmode, DWORD dwpipemode, DWORD nmaxinstances, DWORD noutbuffersize, DWORD ninbuffersize, DWORD ndefaulttimeout, LPSECURITY_ATTRIBUTES lpsecurityattributes ); Argumenty: lpname nazwa potoku, która musi mieć postać: \\.\pipe\nazwa
CreateNamedPipe Argumenty (c.d.): dwopenmode tryb dostępu do potoku - PIPE_ACCESS_DUPLEX zapis i odczyt, - PIPE_ACCESS_INBOUND od klienta do serwera, - PIPE_ACCESS_OUTBOUND od serwera do klienta, oraz dodatkowe flagi: - FILE_FLAG_WRITE_THROUGH bez buforowania; istotne tylko przy komunikacji przez sieć, - FILE_FLAG_OVERLAPPED tryb overlapped (asynchroniczny)
CreateNamedPipe Argumenty (c.d.): dwpipemode tryb pracy potoku, sposób zapisu: - PIPE_TYPE_BYTE zapis do potoku binarny - PIPE_TYPE_MESSAGE zapis tekstowy (tylko w trybie overlapped!) sposób odczytu: - PIPE_READMODE_BYTE odczyt z potoku binarny - PIPE_READMODE_MESSAGE odczyt tekstowy (tylko przy zapisie tekstowym!) oraz blokowanie I/O: - PIPE_WAIT potok blokujący - PIPE_NOWAIT potok nieblokujący (kompatybilność wstecz, niezalecane)
CreateNamedPipe Argumenty (c.d.): nmaxinstances maksymalna liczba instancji potoku (komunikacja serwer wiele klientów) noutbuffersize wielkość bufora wyjściowego (tylko sugestia, ustala system operacyjny) ninbuffersize wielkość bufora wyjściowego (uwagi jw.) ndefaulttimeout domyślny czas oczekiwania na połączenie z potokiem dla funkcji WaitNamedPipe lpsecurityattributes wskaźnik do struktury atrybutów bezp.
CreateNamedPipe Przykład uzycia: HANDLE Pipe; Pipe = CreateNamedPipe ( "\\\\.\\pipe\\testpipe", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE PIPE_WAIT, 1, 2048, 2048, 1000, NULL ); if (Pipe==INVALID_HANDLE_VALUE) //obsługa błędu Funkcja CreateNamedPipe tworzy nazwany potok i zwraca uchwyt do niego albo, w przypadku niepowodzenia, stałą INVALID_HANDLE_VALUE. Aby skorzystać z potoku, trzeba następnie użyć ConnectNamedPipe.
ConnectNamedPipe Funkcja ConnectNamedPipe czeka aż proces klienta połączy się z potokiem utworzonym wcześniej przez funkcję CreateNamedPipe: BOOL ConnectNamedPipe( HANDLE hnamedpipe, LPOVERLAPPED lpoverlapped ); Argumenty: hnamedpipe uchwyt potoku, lpoverlapped wskaźnik struktury Overlapped, używanej przy operacjach I/O asynchronicznych; wymagany dla potoku z flagą FILE_FLAG_OVERLAPPED, dozwolony dla pozostałych
ConnectNamedPipe Przykład uzycia: BOOL Res; Res = ConnectNamedPipe (Pipe, NULL); if (!Res) //obsługa błędu Jeżeli wskaźnik struktury Overlapped nie zostanie podany, funkcja działa synchronicznie, tj. powoduje wstrzymanie procesu aż do połączenia z klientem. Jeżeli potok został utworzony z flagą FILE_FLAG_OVERLAPPED, wskaźnik do struktury Overlapped jest wymagany, zaś sruktura ta musi zawierać uchwyt obiektu Event resetowanego ręcznie.
DisconnectNamedPipe Funkcja DisconnectNamedPipe rozłącza serwerowy koniec nazwanego potoku: BOOL DisconnectNamedPipe( HANDLE hnamedpipe, ); Argumenty: hnamedpipe uchwyt potoku, Jeżeli potok zawiera jeszcze jakiekolwiek nie odczytane dane, zostaną one utracone.
DisconnectNamedPipe Przykład uzycia: BOOL Res; Res = DisconnectNamedPipe(Pipe); if (!Res) //obsługa błędu Jeżeli potok od strony klienta jest otwarty, zostanie wymuszone jego zamknięcie. Klient nie jest o tym informowany, za to następna operacja I/O zakończy się błędem. Klient musi i tak sam zamknąć swój koniec potoku funkcją CloseHandle. Po zamknięciu potoku z obu końców, serwer może użyć potoku do połączenia z następnym klientem.
CreateFile Funkcja CreateFile otwiera strumień I/O, którym może być m.in. plik, konsola lub nazwany potok: HANDLE CreateFile( LPCTSTR lpfilename, DWORD dwdesiredaccess, DWORD dwsharemode, LPSECURITY_ATTRIBUTES lpsecurityattributes, DWORD dwcreationdistribution, DWORD dwflagsandattributes, HANDLE htemplatefile ); Argumenty: lpname nazwa strumienia, która musi mieć postać: \\.\pipe\nazwa; potok musi być wcześniej utworzony funkcją CreateNamedPipe.
CreateFile Argumenty (c.d.): dwdesiredaccess żądany tryb dostępu: GENERIC_READ możliwość odczytu danych, GENERIC_WRITE możliwość zapisu danych; Aby uzyskać prawo odczytu i zapisu, należy podać obie flagi. dwsharemode określa sposób współdzielenia dostępu: FILE_SHARE_DELETE równoczesne kasowanie FILE_SHARE_READ równoczesny odczyt FILE_SHARE_WRITE równoczesny zapis; Jeżeli podana będzie wartość 0, to równoczesne operacje są zabronione, zaś funkcja CreateFile kolejnego procesu spowoduje jego wstrzymanie aż do zamknięcia uchwytu przez pierwszy proces
CreateFile Argumenty (c.d.): lpsecurityattributes wskaźnik do struktury atrybutów bezp. dwcreationdistribution określa sposób traktowania zasobu, zależnie od tego czy już istnieje, czy nie: - CREATE_NEW tworzy nowy zasób (niepowodzenie, jeżeli istnieje) - CREATE_ALWAYS jeżeli istnieje to usuwa, następnie tworzy nowy, - OPEN_EXISTING otwiera istniejący (niepowodzenie, jeżeli nie istnieje) - OPEN_ALWAYS - jeżeli nie istnieje to tworzy, następnie otwiera,
CreateFile Argumenty (c.d.): dwflagsandattributes atrybuty pliku, tj. dowolna kombinacja atrybutów: - FILE_ATTRIBUTE_ARCHIVE, - FILE_ATTRIBUTE_HIDDEN, - FILE_ATTRIBUTE_READONLY, - FILE_ATTRIBUTE_SYSTEM, albo FILE_ATTRIBUTE_NORMAL (brak atrybutów); można też dodać flagi określajace sposób pracy, m.in.: - FILE_FLAG_DELETE_ON_CLOSE, - FILE_FLAG_RANDOM_ACCESS, - FILE_FLAG_SEQUENTIAL_SCAN; - FILE_FLAG_OVERLAPPED tryb overlapped (asynchroniczny); za wyjątkiem FILE_FLAG_OVERLAPPED bez znaczenia dla potoku
CreateFile Argumenty (c.d.): htemplatefile uchwyt do szablonu pliku, z dostępem GENERIC_READ, z którego skopiowane zostaną wszystkie flagi i atrybuty tworzonego pliku (albo NULL). Ważne jest, aby atrybuty przekazane funkcji CreateFile po stronie klienta były zgodne z podanymi dla funkcji CreateNamedPipe po stronie serwera inaczej potoku nie uda się utworzyć.
CreateFile Przykład uzycia: HANDLE Pipe; Pipe = CreateFile ( "\\\\.\\pipe\\testpipe", GENERIC_READ GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (Pipe==INVALID_HANDLE_VALUE) //obsługa błędu Funkcja CreateFile otwiera nazwany potok od strony klienta i zwraca uchwyt do niego albo, w przypadku niepowodzenia, stałą INVALID_HANDLE_VALUE. Serwer musi wcześniej utworzyć potok (CreateNamedPipe) i czekać na połączenie klienta (ConnectNamedPipe).
WriteFile Funkcja WriteFile zapisuje dane do strumienia I/O, którym może być m.in. plik, konsola lub potok: BOOL WriteFile( HANDLE hfile, LPCVOID lpbuffer, DWORD nnumberofbytestowrite, LPDWORD lpnumberofbyteswritten, LPOVERLAPPED lpoverlapped ); Argumenty: hfile uchwyt strumienia I/O, który musi być otwarty oraz mieć dostęp GENERIC_WRITE
WriteFile Argumenty (c.d.): lpbuffer wskaźnik do bufora zawierającego dane do zapisu, nnumberofbytestowrite liczba bajtów do zapisania, lpnumberofbyteswritten wskaźnik do zmiennej, gdzie zostanie wpisana liczba bajtów faktycznie zapisanych; Może być NULL, jeżeli lpoverlapped jest NULL lpoverlapped wskaźnik struktury Overlapped, używanej przy operacjach I/O asynchronicznych; wymagany dla potoku z flagą FILE_FLAG_OVERLAPPED, dozwolony dla pozostałych
WriteFile Przykład uzycia: BOOL res; DWORD len; DWORD knd = 1; CHAR msg[] = "Message"; len = strlen (msg) + 1; WriteFile (Pipe, &knd, sizeof(knd), NULL, NULL); WriteFile (Pipe, &len, sizeof(len), NULL, NULL); res = WriteFile (Pipe, msg, len, NULL, NULL); if (!res) //obsługa błędu Funkcja WriteFile zapisuje dane binarnie, dlatego najbezpieczniej jest stosować metodę TLV (Type-Length-Value). W przypadku potoku, serwer musi wcześniej utworzyć potok (CreateNamedPipe) i zaczekać na połączenie klienta (ConnectNamedPipe), zaś klient połączyć się (CreateFile).
ReadFile Funkcja ReadFile odczytuje dane ze strumienia I/O, którym może być m.in. plik, konsola lub potok: BOOL ReadFile( HANDLE hfile, LPCVOID lpbuffer, DWORD nnumberofbytestoread, LPDWORD lpnumberofbytesread, LPOVERLAPPED lpoverlapped ); Argumenty: hfile uchwyt strumienia I/O, który musi być otwarty oraz mieć dostęp GENERIC_READ
ReadFile Argumenty (c.d.): lpbuffer wskaźnik do bufora na dane do odczytania, nnumberofbytestoread liczba bajtów do odczytania, lpnumberofbytesread wskaźnik do zmiennej, gdzie zostanie wpisana liczba bajtów faktycznie odczytanych; Może być NULL, jeżeli lpoverlapped jest NULL lpoverlapped wskaźnik struktury Overlapped, używanej przy operacjach I/O asynchronicznych; wymagany dla potoku z flagą FILE_FLAG_OVERLAPPED, dozwolony dla pozostałych Jeżeli wskaźnik struktury Overlapped nie zostanie podany, funkcja działa synchronicznie, tj. powoduje wstrzymanie procesu aż do wpisania danych do potoku przez drugi proces.
ReadFile Przykład uzycia: BOOL res; DWORD knd=0, len=0; CHAR msg[100]; ReadFile (Pipe, &knd, sizeof(knd), NULL, NULL); ReadFile (Pipe, &len, sizeof(len), NULL, NULL); if (knd==1 && len>0) { res = ReadFile (Pipe, msg, len, NULL, NULL); if (!res) //obsługa błędu } Funkcja ReadFile odczytuje dane binarnie. Sposób odczytu musi być zgodny ze sposobem zapisu. Najbezpieczniej jest stosować metodę TLV.
PeekNamedPipe Funkcja PeekNamedPipe kopiuje dane z potoku (jednak bez usuwania ich z potoku) oraz dostarcza informacji o stanie potoku: BOOL PeekNamedPipe( HANDLE hnamedpipe, LPVOID lpbuffer, DWORD nbuffersize, LPDWORD lpbytesread, LPDWORD lptotalbytesavail, LPDWORD lpbytesleftthismessage ); Argumenty: hnamedpipe uchwyt potoku (dostarczony przez funkcję CreateNamedPipe lub CreateFile), który musi być otwarty oraz mieć dostęp GENERIC_READ
PeekNamedPipe Argumenty (c.d.): lpbuffer wskaźnik do bufora na dane do odczytania; może być NULL, jeżeli dane nie mają być odczytane, nbuffersize liczba bajtów do odczytania, lpbytesread wskaźnik do zmiennej, gdzie zostanie wpisana liczba bajtów faktycznie odczytanych; może być NULL lptotalbytesavail liczba bajtów dostępnych do odczytania, lpbytesleftthismessage dla trybu PIPE_TYPE_MESSAGE, liczba bajtów bieżącej wiadomości, które nie zmieściły się w buforze Funkcja PeekNamedPipe nie wstrzymuje procesu (jak ReadFile) i nie usuwa odczytanych danych z bufora potoku. Jeżeli parametr nbuffersize ma wartość 0, to funkcja jedynie sprawdza stan potoku, niczego nie odczytując.
PeekNamedPipe Przykład uzycia: BOOL res; DWORD len=0; CHAR msg[100]; PeekNamedPipe (Pipe, NULL, 0, NULL, &len, NULL); if (len>0) { res = ReadFile (Pipe, msg, len, NULL, NULL); if (!res) //obsługa błędu } Funkcja PeekNamedPipe może być wykorzystana do nieblokujących operacji odczytu danych z potoku (zamiast używania flagi PIPE_NOWAIT). Kiedy stosowana jest metoda TLV, funkcja PeekNamedPipe może służyć do odczytania początkowej partii danych, aby określić ich typ oraz rozmiar (aby np. przygotować bufor odpowiedniej wielkości), a także aby sprawdzić, czy kompletne dane są już dostępne do odczytu.
WaitNamedPipe Funkcja WaitNamedPipe wstrzymuje proces, aż potok o wskazanej nazwie będzie dostępny lub do upłynięcia limitu czasu: BOOL WaitNamedPipe( LPCTSTR lpnamedpipename, DWORD ntimeout ); Argumenty: lpname nazwa potoku, która musi mieć postać: \\.\pipe\nazwa ntimeout limit czasu oczekiwania, w milisekundach; można też użyć jednej ze stałych: - NMPWAIT_USE_DEFAULT_WAIT wartość domyślna (podana w funkcji CreateNamedPipe), - NMPWAIT_WAIT_FOREVER do skutku
WaitNamedPipe Przykład uzycia: BOOL res; HANDLE Pipe; res = WaitNamedPipe ( "\\\\.\\pipe\\testpipe", 0); if (res) Pipe = CreateFile (... Funkcja WaitNamedPipe zwraca wartość niezerową jeżeli potok jest dostępny (serwer czeka na połączenie ConnectNamedPipe) lub 0 w przeciwnym wypadku. Potok jest niedostępny, jeżeli nie został utworzony (CreateNamedPipe) albo jest już zajęty. Funkcja ta nie daje pełnej wiedzy o sytuacji (jeżeli potok nie jest dostępny, to nie jest wiadome z jakiego powodu), nie zapewnia też synchronizacji (pozytywny rezultat WaitNamedPipe nie gwarantuje udanego połączenia przy użyciu CreateFile).
OVERLAPPED Struktura OVERLAPPED, potrzebna do asynchronicznych operacji I/O: typedef struct _OVERLAPPED { DWORD Internal; DWORD InternalHigh; DWORD Offset; DWORD OffsetHigh; HANDLE hevent; } OVERLAPPED; Elementy składowe: Internal, InternalHigh zarezerwowane dla OS; nie wolno zmieniać Offset, OffsetHigh pozycja w pliku (w bajtach), począwszy od której ma być wykonana operacja I/O; w przypadku potoków musi być 0, hevent uchwyt zdarzenia (obiektu Event), który zostanie ustawiony w stan sygnalizowany, kiedy tylko operacja I/O zostanie zakończona
OVERLAPPED Przykład uzycia: OVERLAPPED ovr; memset (&ovr, "\0", sizeof(ovr)); ovr.hevent = hovr; ConnectNamedPipe (Pipe, &ovr); // gdzieś indziej: res = WaitForSingleObject (hovr, 0) if (res==wait_object_0) // dzlsze działania Operacje asynchroniczne są intensywnie promowane przez MS. W rzeczywistości są przydatne dopiero wtedy, gdy operacje I/O są licznie i intensywnie wykonywane przez program, np. jeżeli proces serwera obsługuje wiele procesów klientów równocześnie.
OVERLAPPED Asynchroniczne wykonywanie operacji I/O otwiera wiele nowych możliwości, m.in. możliwość obsługi wielu operacji I/O, różnego typu (konsola, pliki, potoki, porty szeregowe i równoległe, sieć komputerowa) równocześnie. Wykonanie asynchroniczne operacji I/O powoduje znaczne skomplikowanie programu np. ze względu na możliwość zakończenia się takiej operacji podczas kiedy proces jest wstrzymany (funkcje WaitFor). Z tego względu pojawiły się rozszerzone wersje wielu funkcji API, m.in.: WaitForSingleObjectEx, WaitForMultipleObjectEx, ReadFileEx, WriteFileEx, SleepEx
Thread Do obsługi wątków dedykowane są funkcje: CreateThread ResumeThread i SuspendThread TerminateThread ExitThread i GetExitCodeThread Ponadto można wykorzystywać: WaitForSingleObject, WaitForMultipleObjects CloseHandle GetLastError
CreateThread Funkcja CreateThread tworzy wątek uruchamiany we wspólnej przestrzeni adresowej z procesem macierzystym: HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpthreadattributes, DWORD dwstacksize, LPTHREAD_START_ROUTINE lpstartaddress, LPVOID lpparameter, DWORD dwcreationflags, LPDWORD lpthreadid ); Argumenty: lpthreadattributes wskaźnik do struktury atrybutów dwstacksize rozmiar stosu (0 jak dla macieżystego procesu)
CreateThread Argumenty (c.d.): lpstartaddress adres funkcji wątku, która typowo ma postać: DWORD WINAPI ThreadFunc(LPVOID lpparameter) { // wykonanie zadania wątku, // z parametrami dostępnymi przez lpparam } return 0; lpparameter adres przez który można przekazać parametry wątku dwcreationflags czy wątek ma od razu wystartować (0), czy czekać na uruchomienie (CREATE_SUSPENDED) lpthreadid adres, pod którym zostanie zapisany identyfikator wątku
CreateThread Przykład uzycia: HANDLE Thr; DWORD ThrID; Thr = CreateThread (NULL, 0, ThreadFunction, &Param, 0, &ThrID); if (Thr==NULL) //obsługa błędu Funkcja ThreadFunction zostanie wykonana jako odrębny wątek. Wykonywanie wątku rozpocznie się natychmiast. Uchwyt (Thr) oraz identyfikator (ThrID) mogą być użyte do zarządzania wątkiem np. uchwyt w ResumeThread, SuspendThread, TerminateThread
ResumeThread Funkcja ResumeThread zmniejsza tzw. licznik zawieszenia wątku. Kiedy licznik osiągnie zero, wykonanie wątku zostaje wznowione: DWORD ResumeThread( HANDLE hthread ); Argumenty: hthread uchwyt wątku Jako rezultat funkcja zwraca poprzednią (sprzed jej wywołania) wartość licznika zawieszenia wątku
ResumeThread Przykład uzycia: HANDLE Thr; DWORD ThrID; Thr = CreateThread (NULL, 0, ThrFoo, NULL, CREATE_SUSPENDED, &ThrID); //gdzieś dalej: ResumeThread (Thr); Wątek utworzony z argumentem dwcreationflags równym CREATE_SUSPENDED nie jest wykonywany (od momentu utworzenia jest zawieszony). Wykonywanie wątku rozpocznie się dopiero po wywołaniu ResumeThread.
SuspendThread Funkcja SuspendThread zawiesza wykonanie wątku i zwiększa licznik zawieszenia wątku: DWORD ResumeThread( HANDLE hthread ); Argumenty: hthread uchwyt wątku Jako rezultat funkcja zwraca poprzednią (sprzed jej wywołania) wartość licznika zawieszenia wątku
SuspendThread Przykład uzycia: HANDLE Thr; Thr = CreateThread (NULL, 0, ThrFoo, NULL, 0, NULL); SuspendThread (Thr); // i gdzieś dalej ResumeThread (Thr); Wątek może być dowolnie zawieszany i wznawiany. Ze względu na licznik zawieszenia, aby doszło do wznowienia wątku, funkcja ResumeThread musi być wywołana tyle samo razy, co wcześniej funkcja SuspendThread (można też sprawdzić i wykorzystać rezultat dostarczany przez funkcję ResumeThread).
ExitThread Funkcja ExitThread kończy wykonywanie wątku: VOID ExitThread( DWORD dwexitcode ); Argumenty: dwexitcode kod wyjścia; może być sprawdzany przez macierzysty proces funkcją GetExitCodeThread W głównej funkcji wątku zamiast ExitThread wystarczy instrukcja return (ExitThread jest wówczas wykonywana niejawnie). Po wykonaniu funkcji ExitThread uchwyt wątku zmienia stan na sygnalizowany, co może być wykryte przez funkcje WaitFor.
ExitThread Przykład uzycia: DWORD WINAPI ThreadFunc (LPVOID lpparameter) { Foo (lpparameter); return 0; } VOID Foo (LPVOID lpparameter) { if (warunek) ExitThread (1); } Zwykle wartość kodu wyjścia 0 oznacza normalne zakończenie, inne wartości sygnalizują błąd wykonania (np. jeżeli wątek nie wykonał swojego zadania). Wartości kodu wyjścia ma znaczenie tylko dla macierzystego procesu system nie bierze go pod uwagę
GetExitCodeThread Funkcja ExitThread kończy wykonywanie wątku: BOOL GetExitCodeThread( HANDLE hthread, LPDWORD lpexitcode ); Argumenty: hthread uchwyt wątku lpexitcode adres, pod który funkcja wpisze kod wyjścia; Jeżeli wykonanie funkcji zakończy się powodzeniem (warunkiem jest aby hthread był uchwytem do wątku, który już zakończył działanie), funkcja zwraca TRUE, a pod wskazany adres wpisuje kod wyjścia wątku. W przeciwnym wypadku funkcja zwraca wartość 0, a szczegóły można uzyskać funkcją GetLastError
GetExitCodeThread Przykład uzycia: HANDLE Thr; DWORD Ext; Thr = CreateThread (NULL, 0, ThrFoo, NULL, 0, NULL); // i gdzieś dalej if (GetExitCodeThread(Thr, &Ext)) // wątek zakończony, kod wyjścia: Ext Zwykle wartość kodu wyjścia 0 oznacza normalne zakończenie, inne wartości sygnalizują błąd wykonania (np. jeżeli wątek nie wykonał swojego zadania).
TerminateThread Funkcja TerminateThread wymusza zakończenie wykonywania wątku: BOOL TerminateThread( HANDLE hthread, DWORD dwexitcode ); Argumenty: hthread uchwyt wątku, który ma być zakończony dwexitcode kod wyjścia; może być sprawdzany funkcją GetExitCodeThread Funkja TerminateThread wymusza zakończenie wątku, nie dając mu (wątkowi) szansy na jakiekolwiek działania kończące (np. zamknięcie otwartych plików, połączeń sieciowych itp.) powinna być używana tylko w wyjątkowych sytuacjach.
ExitThread, WaitForSingleObject Przykład uzycia: HANDLE Thr, Trm; Trm = CreateEvent (NULL, FALSE, FALSE, NULL); Thr = CreateThread (NULL, 0, ThrFoo, NULL, 0, NULL); // i gdzieś dalej SetEvent (Trm); WaitForSingleObject (Thr, INFINITE); // wątek zakończony Znacznie lepiej jest zasygnalizować wątkowi, że powinien kończyć działalność, np. ustawiając dedykowane do tego zdarzenie (Event) w stan sygnalizowany. Oczywiście wątek musi okresowo sprawdzać stan zdarzenia.
RegisterWindowMessage Funkcja RegisterWindowMessage definiuje komunikat systemowy, którego identyfikator jest unikalny: UINT RegisterWindowMessage( LPCTSTR lpstring ); Argumenty: lpstring adres łańcucha znaków identyfikującego komunikat Funkcja dostarcza jako rezultat identyfikator komunikatu (jest to liczba z zakresu 0xC000 do 0xFFFF).
RegisterWindowMessage Przykład uzycia: UINT msgdone; msgdone = RegisterWindowMessage ("Zrobione!"); Dzięki unikalności identyfikatora, komunikat może służyć do sygnalizacji zarówno pomiędzy wątkami jednego procesu, jak i pomiędzy procesami. Jednak aby można było odbierać komunikaty, proces (wątek) musi obsługiwać kolejkę komunikatów. Aplikacje z GUI taką kolejkę mają i obsługują.
PostMessage Funkcja PostMessage wysyła komunikat systemowy: BOOL PostMessage( HWND hwnd, UINT Msg, WPARAM wparam, LPARAM lparam ); Argumenty: hwnd uchwyt okna, do którego wysyłany jest komunikat Msg identyfikator komunikatu (predefiniowany systemu Windows lub uzyskany przez funkcję RegisterWindowMessage) wparam, lparam dodatkowe parametry, które mogą być wykorzystane podczas obsługi komunikatu; znaczenie zależne od komunikatu
SendMessage Funkcja SendMessage wysyła komunikat systemowy i czeka na jego obsłużenie: BOOL SendMessage( HWND hwnd, UINT Msg, WPARAM wparam, LPARAM lparam ); Argumenty: hwnd uchwyt okna, do którego wysyłany jest komunikat Msg identyfikator komunikatu (predefiniowany systemu Windows lub uzyskany przez funkcję RegisterWindowMessage) wparam, lparam dodatkowe parametry jak w PostMessage
PostMessage Przykład uzycia: UINT msgdone; msgdone = RegisterWindowMessage ("Zrobione!"); HANDLE Wnd = Form->Handle; // gdzieś w wątku: PostMessage (Wnd, msgdone, 0, 0); Funkcja umieszcza komunikat w kolejce komunikatów systemowych i natychmiast wraca nie czeka na obsłużenie komunikatu. W Builderze komunikaty takie można przechwytywać w funkcji obsługi zdarzenia OnMessage obiektu ApplicationEvents.
OnMessage Przykład uzycia:... AppMessage(tagMSG &Msg, bool &Handled) { if (Msg.message == msgdone) { // obsługa Handled = true; } } Należy być bardzo ostrożnym przy tworzeniu funkcji obsługi tego zdarzenia błąd może zawiesić całą aplikację. Przez OnMessage przechodzą wszystkie zdarzenia systemowe.
OnMessage Przykład uzycia:... AppMessage(tagMSG &Msg, bool &Handled) { if (Msg.message == msgdone) { // obsługa Handled = true; } } Należy być bardzo ostrożnym przy tworzeniu funkcji obsługi tego zdarzenia błąd może zawiesić całą aplikację. Przez OnMessage przechodzą wszystkie zdarzenia systemowe.
OnMessage Struktura komunikatu jest zdefiniowna jako: typedef struct tagmsg { HWND hwnd; UINT message; WPARAM wparam; LPARAM lparam; DWORD time; POINT pt; } MSG; Pole message jest identyfikatorem komunikatu, pola wparam i lparam (typu DWORD, tj. unsigned long int) pochodzą z wywołania PostMessage i SendMessage.
CriticalSection Do obsługi sekcji krytycznej dedykowane są funkcje: InitializeCriticalSection, EnterCriticalSection, TryEnterCriticalSection LeaveCriticalSection, DeleteCriticalSection, Obiekt CriticalSection łączy w sobie funkcjonalność Muteksu i funkcji WaitFor
InitializeCriticalSection Funkcja InitializeCriticalSection inicjuje obiekt sekcji krytycznej: VOID InitializeCriticalSection( LPCRITICAL_SECTION lpcriticalsection ); Argumenty: lpcriticalsection wskaźnik do struktury typu CRITICAL_SECTION
InitializeCriticalSection Przykład uzycia: CRITICAL_SECTION Sct; InitializeCriticalSection (&Sct); Zwykle główny wątek programu tworzy (poprzez zadeklarowanie) i przy pomocy InitializeCriticalSection inicjuje sekcję krytyczną. Z obiektu tego mogą następnie korzystać wszystkie wątki współdzielące zasoby.
EnterCriticalSection Funkcja EnterCriticalSection czeka na uzyskanie wyłącznego dostępu do sekcji krytycznej: VOID EnterCriticalSection( LPCRITICAL_SECTION lpcriticalsection ); Argumenty: lpcriticalsection wskaźnik do struktury typu CRITICAL_SECTION
LeaveCriticalSection Funkcja EnterCriticalSection powoduje zrzeczenie się wyłącznego dostępu do sekcji krytycznej: VOID LeaveCriticalSection( LPCRITICAL_SECTION lpcriticalsection ); Argumenty: lpcriticalsection wskaźnik do struktury typu CRITICAL_SECTION
EnterCriticalSection, LeaveCriticalSection Przykład uzycia: CRITICAL_SECTION Sct; InitializeCriticalSection (&Sct); // gdzieś dalej EnterCriticalSection (&Sct); // sekcja krytyczna LeaveCriticalSection (&Sct); Funkcja powoduje zawieszenie procesu (wątku) aż do uzyskania wyłącznego dostępu do sekcji krytycznej. Po wykonaniu funkcji, wątek staje się właścicielem sekcji krytycznej i może swobodnie korzystać ze współdzielonych zasobów.
TryEnterCriticalSection Funkcja TryEnterCriticalSection czeka na uzyskanie wyłącznego dostępu do sekcji krytycznej: BOOL TryEnterCriticalSection( LPCRITICAL_SECTION lpcriticalsection ); Argumenty: lpcriticalsection wskaźnik do struktury typu CRITICAL_SECTION
TryEnterCriticalSection Przykład uzycia: if (TryEnterCriticalSection (&Sct)) { // sekcja krytyczna LeaveCriticalSection (&Sct); } Funkcja nie powoduje zawieszenie procesu (wątku). Funkcja sprawdza, czy jest możliwe uzyskanie wyłącznego dostępu do sekcji krytycznej. Jeżeli tak, to funkcja zwraca TRUE, a wątek staje się właścicielem sekcji krytycznej i może swobodnie korzystać ze współdzielonych zasobów. W przeciwnym razie zwraca FALSE. Używanie TryEnterCriticalSection zamiast EnterCriticalSection może powodować problemy z żywotnością -
DeleteCriticalSection Funkcja DeleteCriticalSection zwalnia wszystkie zasoby używane przez sekcję krytyczną: BOOL DeleteCriticalSection ( LPCRITICAL_SECTION lpcriticalsection ); Argumenty: lpcriticalsection wskaźnik do struktury typu CRITICAL_SECTION
DeleteCriticalSection Przykład uzycia: CRITICAL_SECTION Sct; InitializeCriticalSection (&Sct); // gdzieś dalej EnterCriticalSection (&Sct); // sekcja krytyczna LeaveCriticalSection (&Sct); // na koniec DeleteCriticalSection (&Sct); Nie jest to obowiązkowe (zasoby zostaną i tak zwolnione po zakończeniu programu), jednak zalecane, ponieważ odciąża system.
Mutex Do obsługi muteksów dedykowane są funkcje: CreateMutex OpenMutex ReleaseMutex Ponadto można wykorzystywać: WaitForSingleObject, WaitForMultipleObjects CloseHandle GetLastError
CreateMutex Funkcja CreateMutex tworzy nazwany lub nienazwany muteks i zwraca uchwyt do niego: HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpmutexattributes, BOOL binitialowner, LPCTSTR lpname ); Argumenty: lpmutexattributes wskaźnik do struktury atrybutów binitialowner flaga okreslająca, czy proces tworzący ma być początkowo w posiadaniu muteksu lpname nazwa muteksu
CreateMutex Przykład uzycia: HANDLE Mux; Mux = CreateMutex (NULL, FALSE, NULL); if (Mux==NULL) //obsługa błędu Muteks bez nazwy (jako trzeci argument należy podać NULL) może być użyty tylko przez jeden proces (nie ma możliwości przekazania uchwytu do innego procesu) przydaje się w aplikacjach wielowątkowych. Ze względu na brak nazwy, nie można uzyskać uchwytu do muteksu przy pomocy funkcji OpenMutex
WaitForSingleObject Funkcja WaitForSingleObject czeka na ustawienie obiektu w stan sygnalizowania: DWORD WaitForSingleObject( HANDLE hhandle, DWORD dwmilliseconds ); Argumenty: hhandle uchwyt obiektu, na który funkcja czeka dwmilliseconds czas oczekiwania w milisekundach może być 0 (funkcja de facto nie czeka, a sprawdza), wartość niezerowa lub stała INFINITE (do skutku)
WaitForSingleObject Zwracane wartości: WAIT_ABANDONED muteks nie został zwolniony, lecz proces, który go posiadał został zakończony (co może oznaczać błąd wykonania sekcji krytycznej) WAIT_OBJECT_0 wystąpiło oczekiwane zdarzenie WAIT_TIMEOUT upłynął limit czasu dwmilliseconds i nic się nie stało Funkcja WaitForSingleObject zmienia stan też obiektu np. muteks zostanie ustawiony w stan zajętości zasobu proces staje się jego wyłącznym właścicielem i może wejść do sekcji krytycznej.
ReleaseMutex Funkcja ReleaseMutex zwalnia muteks: BOOL ReleaseMutex( HANDLE hmutex ); Argumenty: hmutex uchwyt zwalnianego muteksu Proces przestaje być właścicielem muteksu, co pozwala przejąć muteks przez inny proces. Zwolnienie muteksu powinno nastąpić natychmiast po wyjściu z sekcji krytycznej.
WaitForSingleObject, ReleaseMutex Przykład uzycia: HANDLE Mux; Mux = CreateMutex (NULL, FALSE, "Nazwa"); while (1) { WaitForSingleObject (Mux, INFINITE); // sekcja krytyczna ReleaseMutex (Mux); if (warunek) break; }
WaitForMultipleObjects Funkcja WaitForMultipleObjects czeka na ustawienie jednego lub wszystkich obiektu w stan sygnalizowania: DWORD WaitForMultipleObjects( DWORD ncount, CONST HANDLE *lphandles, BOOL bwaitall, DWORD dwmilliseconds ); Argumenty: ncount liczba obiektów w tablicy lphandles lphandles wskaźnik tablicy z uchwytami obiektów bwaitall flaga określająca, czy czekać na wszystkie obiekty, czy na którykolwiek z nich dwmilliseconds czas oczekiwania (0 INFINITE)
WaitForMultipleObjects Zwracane wartości: WAIT_ABANDONED_0 pierwszy w tablicy muteks nie został zwolniony, lecz proces, który go posiadał został zakończony WAIT_ABANDONED_0 + n jw., dla n-tego muteksu WAIT_OBJECT_0 wystąpiło pierwsze oczekiwane zdarzenie WAIT_OBJECT_0 + n jw., dla n-tego obiektu WAIT_TIMEOUT upłynął limit czasu dwmilliseconds i nic się nie stało Funkcja WaitForMultipleObjects zmienia stan też obiektu, analogicznie jak funkcja WaitForSingleObject.
WaitForMultipleObjects Przykład uzycia: HANDLE MuxTab[2]; // utworzenie wszystkich muteksów while (1) { Res = WaitForMultipleObjects(2, MuxTab, FALSE, INFINITE); } if (Res== WAIT_OBJECT_0) { // sekcja krytyczna 0 ReleaseMutex (MuxTab[0]); if (warunek) break; } if (Res== WAIT_OBJECT_0 + 1) { // sekcja krytyczna 1 ReleaseMutex (MuxTab[1]); if (warunek) break; }
CloseHandle Funkcja CloseHandle zamyka obiekt inedtyfikowany przez uchwyt: BOOL CloseHandle( HANDLE hobject ); Argumenty: hobject uchwyt zamykanego obiektu Obiekt nie zostanie zniszczony, dopóki korzystają z niego inne procesy. Dopiero po zamknięciu uchwytu przez ostatni z nich, obiekt jest niszczony. Jawne zamykanie uchwytów nie jest niezbędne system zamyka wszystkie uchwyty w chwili zakończenia procesu jednak zalecane, kiedy obiekt jest niepotrzebny odciąża to system.
Event Do obsługi zdarzeń dedykowane są funkcje: CreateEvent OpenEvent SetEvent, PulseEvent ResetEvent Ponadto można wykorzystywać: WaitForSingleObject, WaitForMultipleObjects CloseHandle GetLastError
CreateEvent Funkcja Create Event tworzy nazwane lub nienazwane zdarzenie i zwraca uchwyt do niego: HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpmutexattributes, BOOL bmanualreset, BOOL binitialstate, LPCTSTR lpname ); Argumenty: lpmutexattributes wskaźnik do struktury atrybutów bmanualreset czy zdarzenie ma być resetowane przez ResetEvent, czy przez system (w rezultacie funkcji WaitForSingle/MultipleObject) binitialstate czy w momencie utworzenia zdarzenie ustawione lpname nazwa zdarzenia
CreateEvent Przykład uzycia: HANDLE Ev; Ev = CreateEvent(NULL, FALSE, FALSE, "Nazwa"); if (Ev==NULL) //obsługa błędu Zdarzenie bez nazwy (podobnie jak Muteks) może być użyte tylko przez jeden proces przydaję się w aplikacjach wielowątkowych. Zdarzenie nazwane może być użytkowane przez różne procesy. Kolejne procesy mogą uzyskać uchwyt do zdarzenia (o ile tylko znają jego nazwę) przy pomocy funkcji OpenEvent
OpenEvent Funkcja OpenEvent zwraca uchwyt istniejącego zdarzenia: HANDLE OpenEvent( DWORD dwdesiredaccess, BOOL binherithandle, LPCTSTR lpname ); Argumenty: dwdesiredaccess EVENT_ALL_ACCESS lub EVENT_MODIFY_STATE binherithandle czy uchwyt może być dziedziczony lpname nazwa zdarzenia
OpenEvent Przykład uzycia: HANDLE Ev; Ev = OpenEvent(EVENT_ALL_ACCESS, FALSE, "Nazwa"); if (Ev==NULL) //obsługa błędu Funkcja zakończy się niepowodzeniem, jeżeli zdarzenie nie istnieje (lub nazwa nie zostanie podana). Szczegóły można uzyskać funkcją GetLastError
SetEvent Funkcja SetEvent ustawia zdarzenie w stan sygnalizowany: BOOL SetEvent( HANDLE hevent ); Argumenty: hevent uchwyt zdarzenia, które ma być ustawione Ustawienie zdarzenia w stan sygnalizowany jest wykrywane przez funkcje WaitForSingle/MultipleObject. Wszystkie procesy czekajace na zdarzenie zostaną wznowione.
PulseEvent Funkcja PulseEvent ustawia zdarzenie w stan sygnalizowany, a następnie przestawia je w stan nie sygnalizowany: BOOL PulseEvent( HANDLE hevent ); Argumenty: hevent uchwyt zdarzenia, które ma być ustawione Ustawienie zdarzenia w stan sygnalizowany jest wykrywane przez funkcje WaitForSingle/MultipleObject. Jeżeli zdarzenie jest resetowane automatycznie, tylko jeden oczekujący proces zostanie wznowiony; jeżeli resetowanie jest ręczne wszystkie procesy.
ResetEvent Funkcja ResetEvent resetuje zdarzenie (przestawia zdarzenie w stan nie sygnalizowany): BOOL ResetEvent( HANDLE hevent ); Argumenty: hevent uchwyt zdarzenia, które ma być ustawione Zdarzenie jest resetowane przez WaitForSingle/MultipleObject, o ile argument bmanualreset funkcji CreateEvent był równy FALSE. W przeciwnym wypadku zdarzenie jest w stanie sygnalizowanym aż do wykonania ResetEvent.
CreateEvent, SetEvent Przykład uzycia: HANDLE Ev; Ev = CreateEvent(NULL, FALSE, FALSE, "Nazwa"); // gdzieś dalej: if (warunek) { SetEvent (Ev); }
OpenEvent, WaitForSingleObject Przykład uzycia: HANDLE Ev; Ev = OpenEvent(EVENT_ALL_ACCESS, FALSE, "Nazwa"); while (1) { if (WaitForSingleObject (Ev, 0)== WAIT_OBJECT_0) // reakcja na zdarzenie if (warunek) break; }