Cvičení č. 6 Synchronizace ve Windows 3 Body
Datum: 3.4.2008 1
Obsah 1. Úvod.............................................................................................................................................2 2. Pokyny pro odevzdání..................................................................................................................2 3. Příprava ........................................................................................................................................2 4. Úlohy............................................................................................................................................3 4.1 Doporučení pro implementaci................................................................................................3 4.5 Přehled použitých funkcí Windows .......................................................................................4
1. Úvod V tomto cvičení si ukážeme prostředky pro synchronizaci prováděcích toků a procesů v prostředí Windows. Vytvoříme několik ukázkových programů s různými způsoby synchronizace. Cílem cvičení je získat představu o synchronizačních objektech dostupných v prostředí Windows a na řešení modelových úloh získat praktické dovednosti v této oblasti. V rámci cvičení bude upravováno několik ukázkových programů, které využívají následující synchronizační objekty Windows: kritické sekce, mutexy, semafory, události.
2. Pokyny pro odevzdání Výstupem cvičení bude protokol – krátká zpráva o řešení úloh na cvičení. Šablonu protokolu si stáhněte z Moodle. Protokol by měl obsahovat odpovědi na otázky, zdrojový kód Vašich řešení podle zadání, případně stručný popis úprav v ukázkovém kódu podle bodu 4 v tomto dokumentu. Protokol pojmenujte svým jménem ve formátu Prijmeni_Jmeno a odevzdejte na Moodle.
3. Příprava Prostudujte si prezentaci na Moodle Windows - synchronizace. Pro oživení základních pojmů si může znovu pročíst také dokument Synchronizace dostupný na Moodle v jedné z předchozích lekcí, která se zabývala synchronizací.
2
4. Úlohy • • • •
•
•
Stáhněte si z Moodle ukázkové programy (synchron_win_ukazky.zip). Jednotlivé programy postupně přeložte a spusťte. V programech proveďte úpravy (vyřešte úkoly) podle popisu v hlavičkách jednotlivých souborů. Úkoly jsou také shrnuty dále. MUTEXY: Upravte thread_cteni tak, aby čekala na mutex maximálně 50 ms. Pokud mutex do té doby nezíská, vypíše hlášení na obrazovku a nebude pracovat se sdílenými daty. Pokud mutex získá, vypíše obsah sdílené proměnné jako v původním programu. SEMAFORY: Přidejte do programu ještě jeden prováděcí tok, který bude zobrazovat hodnotu ze sdílené proměnné a upravte semafor tak, aby mohly ze sdílené proměnné číst současně dva toky (zvětšete počáteční hodnotu počitadla semaforu na 2). Při uvolňování semaforu vypisujte na obrazovku hodnotu jeho počitadla (parametr lpPreviousCount). POZOR: funkce pro zápis musí volat dvakrát funkci WaitForSingleObject, aby získala "obě hodnoty" semaforu. Jen tak bude zajištěno, že v době zápisu ze sdílené proměnné některý prováděcí tok nebude současně číst. UDÁLOSTI: Vysvětlete, jaký problém může nastat v ukázkovém programu při přístupu ke sdílené proměnné g_nData. Vyřešte tento problém s použitím ještě jedné události (ta bude signalizovat dokončení práce s naměřenými daty). Do protokolu uveďte řešení jednotlivých úkolů. Vložte pouze kód v nezbytném rozsahu tak, aby bylo zřejmé řešení úkolů. Kód doplňte také stručným slovním popisem řešení.
4.1 Doporučení pro implementaci Čekání na mutex s určením maximální doby čekání: Funkce WaitForSingleObject vrací jednu ze tří konstant, podle které lze určit výsledek čekání: WAIT_OBJECT_0 – stav objektu, na který se čekalo byl signalizován. Jinak řečeno čekání bylo úspěšné. Toto je návratový kód, který odpovídá příznivému výsledku = získání např. mutexu. WAIT_TIMEOUT – uplynula maximální doba čekání (timeout) aniž by byl objekt signalizován. Např. mutex je tedy vlastně jiným prováděcím tokem a nebyl dosud uvolněn. WAIT_ABANDONED – Mutex nebyl uvolněn prováděcím tokem, který už ale skončil. To znamená, že je v prováděcím toku chyba, a tok skončil, aniž by uvolnil mutex, který si přivlastnil. Náš tok tedy může se sdílenými daty chráněnými tímto mutexem pracovat. Měli bychom ale především opravit chybu v toku, který mutex neuvolnil. Příklad použití timeout při čekání na mutex: DWORD r = WaitForSingleObject(hMutex, 50); if ( r == WAIT_TIMEOUT ) { printf("Mutex nebyl ziskan v pozadovanem case.\n Cekam znovu..."); }
Poznámka: Funkce WaitForSingleObject je podrobně popsána na konci tohoto dokumentu.
3
Semafor s možností několika čtoucích thread, ale pouze jedné zapisující: Semafor má hodnotu počitadla 2, tj. dva prováděcí toky jej mohou získat současně a číst ze sdílených dat. Prováděcí tok, který sdílená data mění se ale musí vyvarovat změny v době, kdy některý tok data čte. Proto volá před zápisem funkci WaitForSingleObject dvakrát, čímž sníží hodnotu počitadla semaforu na nulu. Tím je zaručeno že žádná jiná thread v době zápisu nebude data číst. Získání semaforu dvakrát před zápisem do sdílené proměnné z níž mohou najednou číst dva prováděcí toky tedy vypadá takto: WaitForSingleObject(hSemafor, INFINITE); WaitForSingleObject(hSemafor, INFINITE); // nyní pracovat se sdílenou proměnnou… // Uvolnit semafor – POZOR: pocitadlo zvysime o 2. ReleaseSemaphore(hSemafor, 2, NULL );
Všimněte si, že funkci ReleaseSemaphore je jako druhý parametr předáno 2, tj. počitadlo semaforu se zvýší o 2 - protože jsme předtím čekali na semafor dvakrát a nyní chceme zase umožnit číst sdílená data až dvěma tokům současně. Čtoucí toky získávají i uvolňují semafor pouze jednou (volají ReleaseSemaphore s hodnotou 1).
4.5 Přehled použitých funkcí Windows Práce s kritickými sekcemi void WINAPI InitializeCriticalSection( __out LPCRITICAL_SECTION lpCriticalSection); Inicializuje objekt kritické sekce. Parametry: lpCriticalSection - ukazatel na objekt kritické sekce, který se má inicializovat. Tento objekt definujeme následovně: CRITICAL_SECTION Sekce; void WINAPI EnterCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection); Čeká na vlastnictví kritické sekce. Funkce vrátí až když je volajícímu prováděcímu toku přiděleno vlastnictví kritické sekce. Parametry: lpCriticalSection – ukazatel na objekt kritické sekce, jejíž vlastnictví se má získat. void WINAPI LeaveCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection); Vzdá se vlastnictví kritické sekce. Parametry: lpCriticalSection - ukazatel na objekt kritické sekce, jejíž vlastnictví se uvolňuje.
4
Práce s mutexy HANDLE WINAPI CreateMutex( __in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, __in BOOL bInitialOwner, __in_opt LPCTSTR lpName); Funkce vytvoří mutex. Parametry: lpMutexAttributes – nastavení zabezpečení mutexu. NULL pro výchozí hodnotu. bInitialOwner - Pokud je TRUE, volající prováděcí tok získá zároveň s vytvořením mutexu jeho vlastnictví. lpName – jméno mutexu. Může být NULL pokud mutex nemá mít jméno. Poznámka: jméno je nutné pokud je mutex využívám více procesy. Funkce vrací handle vytvořeného mutexu nebo NULL pokud selže. BOOL WINAPI ReleaseMutex( __in HANDLE hMutex ); Vzdá se vlastnictví daného mutexu. Parametry: hMutex – handle mutexu, jehož vlastnictví se uvolňuje. Poznámka: Pro získání vlastnictví mutexu se využije některá z „čekacích funkcí“, např. WaitForSingleObject, viz níže.
Práce se semafory HANDLE WINAPI CreateSemaphore( __in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, __in LONG lInitialCount, __in LONG lMaximumCount, __in_opt LPCTSTR lpName); Funkce vytvoří semafor. Parametry: lpSemaphoreAttributes – nastavení zabezpečení semaforu. NULL pro výchozí hodnotu. lInitialCount – počáteční hodnota počitadla semaforu lMaximumCount – maximální hodnota počitadla semaforu. lpName - jméno semaforu. Může být NULL pokud semafor nemá mít jméno. Poznámka: jméno je nutné pokud je semafor využívám více procesy. Funkce vrací handle vytvořeného semaforu nebo NULL pokud selže.
5
BOOL WINAPI ReleaseSemaphore( __in HANDLE hSemaphore, __in LONG lReleaseCount, __out_opt LPLONG lpPreviousCount ); Vzdá se vlastnictví semaforu. Parametry: hSemaphore – handle semaforu jehož vlastnictví se uvolňuje. lReleaseCount – hodnota, o kterou se má zvýšit počitadlo semaforu. lpPreviousCount – ukazatel na proměnnou, do které bude uložena předchozí hodnota počitadla semaforu (před zvýšením touto funkcí). Může být NULL pokud hodnota není požadována. Poznámka: Pro získání vlastnictví semaforu se využije některá z „čekacích funkcí“, např. WaitForSingleObject, viz níže.
Práce s událostmi HANDLE WINAPI CreateEvent( __in_opt LPSECURITY_ATTRIBUTES lpEventAttributes, __in BOOL bManualReset, __in BOOL bInitialState, __in_opt LPCTSTR lpName); Funkce vytvoří objekt události. Parametry: lpEventAttributes – nastavení zabezpečení pro vytvářený objekt události. NULL pro výchozí hodnotu. bManualReset – TRUE pokud má být vytvořená událost ručně resetovaná (manual reset). Taková událost zůstává po signalizaci v signalizovaném stavu až do resetování voláním funkce ResetEvent. Důsledkem je, že všechny prováděcí toky, které na tuto událost čekají jsou probuzeny. Pokud je parametr FALSE, je vytvořena automaticky resetovaná událost. Po signalizaci takové události je vždy probuzen pouze jeden z prováděcích toků, které na ni čekají a událost je automaticky resetována. bInitialState – TRUE pokud má být událost po vytvoření také signalizována. lpName - jméno události. Může být NULL pokud událost nemá mít jméno. Poznámka: jméno je nutné pokud je událost využívána pro komunikaci mezi procesy. Prováděcí toky v rámci jednoho procesu událost používají prostřednictvím globální proměnné (handle). Funkce vrací handle vytvořené události nebo NULL pokud selže.
6
BOOL WINAPI SetEvent(__in HANDLE hEvent); Signalizuje danou událost. Parametry: hEvent – handle události, která se má signalizovat. Poznámka: Pro čekání na událost se využije některá z „čekacích funkcí“, např. WaitForSingleObject, viz níže.
Ostatní funkce DWORD WINAPI WaitForSingleObject(HANDLE hHandle, DWORD dwMilisecond ); Funkce čeká na zadaný synchronizační objekt dokud nezíská jeho vlastnictví nebo dokud neuplyne nastavený čas. Parametry: hHandle – handle synchronizačního objektu, na který se má čekat, např. mutexu, semaforu nebo události. dwMiliseconds – maximální doba čekání v milisekundách. INFINITE pokud se má čekat neomezenou dobu. Funkce vrací jednu ze tří hodnot: o WAIT_OBJECT_0 – stav objektu, na který se čekalo byl signalizován. Jinak řečeno, čekání bylo úspěšné. Toto je návratový kód, který odpovídá příznivému výsledku, získání např. mutexu. o WAIT_TIMEOUT – uplynula maximální doba čekání (timeout) aniž by byl objekt signalizován. Např. mutex je vlastněn jiným prováděcím tokem a nebyl dosud uvolněn. o WAIT_ABANDONED – Mutex nebyl uvolněn prováděcím tokem, který už ale skončil. To znamená, že je v prováděcím toku chyba, a tok skončil, aniž by uvolnil mutex, který si přivlastnil. Náš tok může tedy v podstatě se sdílenými daty chráněnými tímto mutexem pracovat. Měli bychom ale především opravit chybu v toku, který mutex neuvolnil. Poznámka: Příklad použití funkce je uveden výše v tomto dokumentu. BOOL WINAPI CloseHandle(__in HANDLE hObject); Funkce zavírá handle objektu (mutexu, semaforu, thread atd.) Poznámka: Voláním funkce je v systému sníženo interní počitadlo odkazů na daný objekt (např. mutex) a pokud počitadlo dosáhne nuly, je objekt odstraněn ze systému. Všimněte si, že v ukázkových programech voláme tuto funkci po vytvoření prováděcího toku. Pokud bychom tak neučinili, i po skončení běhu příslušného toku by jeho objekt stále existoval (a zabíral paměť) protože handle toku by nebylo zavřeno. Parametry: hObject – handle objektu, který se má zavřít.
7
DWORD WINAPI GetTickCount(void); Funkce vrací počet milisekund, které uběhly od startu systému. Po 49,7 dne hodnota přeteče a pokračuje znovu od nuly. VOID WINAPI Sleep(__in DWORD dwMilliseconds); Pozastaví vykonávání aktuálního prováděcího toku na zadaný počet milisekund. Parametry: dwMilliseconds - Doba na kterou má být thread pozastavena, v milisekundách. Hodnota 0 způsobí, že se volající thread vzdá zbytku svého časového kvanta (time slice).
Podrobnosti k jednotlivým funkcím můžete najít v nápovědě Visual Studio nebo online na MSDN.
5. Odkazy Moodle – podpora předmětu na http://vyuka.fai.utb.cz. Magisterské Studium, Programování realtime aplikací. Klíč k zápisu je PR2008. MSDN Library – Online dokumentace k funkcím a produktům Microsoft. http://msdn2.microsoft.com/en-us/library/default.aspx
8