Choď na obsah Choď na menu
 

Vlákna

http://phoenix.inf.upol.cz/~outrata/courses/os2/texts/synch1_w.html

 

Windows - Synchronizace (Synchronization)

Win32 API poskytuje mnoho způsobů koordinace vykonávání více vláken a mechanizmů synchronizace přístupu k prostředku. První metoda je použití synchronizačních objektů v čekacích funkcích. Stav objektu je buď signalizován nebo nesignalizován. Čekací funkce blokují vykonávání vlákna, dokud není nesignalizovaný objekt signalizován. Další synchronizační mechanizmus je např. objekt kritické sekce.

Čekací funkce (Wait functions)

Existují tři typy čekacích funkcí: single-object, multiple-object a alertable. Tyto funkce se neukončí, dokud není splněna nějaká podmínka. Když je funkce zavolána, zkontroluje, zda je podmínka splněna. Pokud ne, vlákno vstoupí do čekacího stavu, kde spotřebovává velmi málo času procesoru a čeká, až bude podmínka splněna.

Single-object čekací funkce jsou WaitForSingleObject a SignalObjectAndWait.

 

WaitForSingleObject
  • ukončí se, když je synchronizační objekt (hHandle) signalizován nebo vyprší čas v dwMilliseconds (který může být INFINITE, tj. nevyprší nikdy)
  • vrací WAIT_OBJECT_0 při signalizování objektu, WAIT_TIMEOUT při vypršení času nebo WAIT_FAILED při chybě

 

 

SignalObjectAndWait
  • atomicky signalizuje synchronizační objekt (hObjectToSignal) a čeká, až bude jiný objekt (hObjectToWaitOn) signalizován
  • ukončí se také po vypršení času v dwMilliseconds (který může být INFINITE, tj. nevyprší nikdy)
  • hObjectToSignal může být handle semaforu, mutexu nebo události
  • vrací WAIT_OBJECT_0 při signalizování objektu, WAIT_TIMEOUT při vypršení času nebo 0xFFFFFFFF při chybě

 

Multiple-object čekací funkce jsou WaitForMultipleObjects a MsgWaitForMultipleObjects.

 

WaitForMultipleObjects
  • ukončí se, když je některý nebo všechny synchronizační objekty (lpHandles) signalizovány nebo vyprší čas v dwMilliseconds (který může být INFINITE, tj. nevyprší nikdy)
  • v nCount je počet objektů, maximum je MAXIMUM_WAIT_OBJECTS
  • bWaitAll specifikuje, zda se má čekat na signalizaci všech objektů (TRUE), nebo jen na signalizaci libovolného jednoho (FALSE)
  • při čekání na všechny objekty funkce nemění stav objektů, dokud nejsou signalizovány všechny, např. mutex je signalizován, ale funkce se neukončí, dokud nebudou signalizovány všechny objekty, během tohoto čekání může jiné vlákno vrátit mutex do nesignalizovaného stavu atd.
  • vrací hodnotu z intervalu WAIT_OBJECT_0 až WAIT_OBJECT_0 + nCount - 1, po odečtení WAIT_OBJECT_0 je to nejmenší index signalizovaného objektu, WAIT_TIMEOUT při vypršení času nebo WAIT_FAILED při chybě

 

 

MsgWaitForMultipleObjects
  • jako WaitForMultipleObjects, ale některé objekty mohou být objekty události vstupu (např. vstup od myši), u kterých se čeká, až přijdou do vstupní fronty vlákna
  • vlákno může použít GetMessage nebo PeekMessage na získání vstupu
  • dwWakeMask specifikuje typ vstupu, pro který bude přidán objekt události vstupu, může být libovolná kombinace např. QS_ALLINPUT (jakákoliv zpráva), QS_HOTKEY (zpráva WM_HOTKEY), QS_INPUT (vstupní zpráva), QS_KEY (klávesové zprávy), QS_MOUSE (zprávy události myši), QS_TIMER (zpráva WM_TIMER) a další
  • vrací WAIT_OBJECT_0 + nCount při příchodu objektu události vstupu, při chybě 0xFFFFFFFF

 

Před svým ukončením může čekací funkce změnit stav synchronizačního objektu, na který čekala, např. snížit hodnotu semaforu o 1 (ten je nesignalizován, pokud jeho hodnota je 0) nebo nastavit na nesignalizovaný stav mutex, auto-reset událost, change-notification objekt a synchronizační časovač.

Čekací funkce se musí používat opatrně ve spojení s DDE. Pokud vlákno vytvoří okno, musí obsluhovat zprávy. DDE posílá zprávy všem oknům v systému. Pokud vlákno čeká bez časového omezení, nastane deadlock. Vlákna, která tvoří okna, musí používat MsgWaitForMultipleObjects.

Synchronizační objekty (Synchronization objects)

Synchronizační objekt se zadává (jeho handle) v čekacích funkcích ke koordinaci vykonávání více vláken. Stejný objekt může využívat i více procesů pro meziprocesní synchronizaci. Pro synchronizaci se používají objekty události, semaforu, mutexu a časovače. Ale mohou se použít i objekty procesu a vlákna, které jsou při běhu nesignalizovány a signalizovány při ukončení.

Objekt události (Event object)

Jsou dva typy objektu události:

  • manual-reset - zůstane signalizován, dokud není explicitně nastaven na nesignalizován, když je signalizován, může se odblokovat libovolný počet čekajících vláken
  • auto-reset - zůstane signalizován, dokud není odblokováno jedno čekající vlákno, objekt je pak nastaven na nesignalizován

 

Objekt události je užitečný pro zaslání signálu pro indikaci, že se objevila nějaká událost (např. se dokončila nějaká operace).

 

CreateEvent
  • vytvoří (nebo vrací) objekt události se jménem lpName (může být NULL), defaultně práva EVENT_ALL_ACCESS a nedědí se
  • bManualReset specifikuje typ objektu, manual-reset při TRUE
  • bInitialState určuje počáteční stav, signalizován při TRUE
  • vrací handle objektu, při chybě NULL
  • pro zrušení stačí CloseHandle, při ukončení procesu se zruší automaticky

 

 

OpenEvent
  • vrací handle existujícího objektu události se jménem lpName, při chybě NULL
  • dwDesiredAccess specifikuje přístupová práva objektu, EVENT_ALL_ACCESS pro všechna, EVENT_MODIFY_STATE pro modifikaci
  • bInheritHandle povoluje dědění (TRUE)

 

 

SetEvent
  • nastaví stav objektu události (hEvent) na signalizován

 

 

PulseEvent
  • jako SetEvent, ale po odblokování vrátí objekt na nesignalizovaný stav

 

 

ResetEvent
  • nastaví stav objektu události (hEvent) na nesignalizován
  • používá se u manual-reset objektu

 

Př. Napište program, ve kterém vytvoříte vlákno. Toto vlákno bude monitorovat stav objektu události a při signalizovaném stavu se ukončí. Objekt signalizujte z prvního vlákna.

Semafor (Semaphore object)

Semafor je "počítadlo", které má hodnotu od 0 do maximální hodnoty. Hodnota je dekrementována (o 1) po každé čekací funkci a inkrementována, když vlákno semafor uvolní. Když je hodnota 0, čekací funkce vlákno blokuje. Semafor je signalizován, když je jeho hodnota větší než 0, a nesignalizován, když je 0.

Semafor je užitečný ke kontrolování sdíleného prostředku, ke kterému má přístup omezený počet uživatelů. Např. limit na počet vytvořených oken. Maximální hodnota semaforu je maximální počet oken, dekrementuje se při vytvoření okna, inkrementuje při zavření. Před vytvořením okna se volá čekací funkce se semaforem.

 

CreateSemaphore
  • vytvoří (nebo vrací) semafor se jménem lpName (může být NULL), defaultně práva SEMAPHORE_ALL_ACCESS a nedědí se
  • lInitialCount určuje počáteční hodnotu semaforu (která je od 0 do maximální hodnoty), lMaximumCount maximální
  • vrací handle objektu, při chybě NULL
  • pro zrušení stačí CloseHandle, při ukončení procesu se zruší automaticky

 

 

OpenSemaphore
  • vrací handle existujícího semaforu se jménem lpName, při chybě NULL
  • dwDesiredAccess specifikuje přístupová práva objektu, SEMAPHORE_ALL_ACCESS pro všechna, SEMAPHORE_MODIFY_STATE pro inkrementaci
  • bInheritHandle povoluje dědění (TRUE)

 

 

ReleaseSemaphore
  • inkrementuje hodnotu semaforu (hSemaphore) o lReleaseCount
  • pokud by hodnota po inkrementaci byla větší než maximální hodnota, k inkrementaci nedojde a funkce vrací FALSE
  • na adresu lpPreviousCount (pokud není NULL) se uloží předchozí hodnota

 

Počáteční hodnota semaforu je typicky maximální hodnota. Je dekrementována při spotřebovávání chráněného prostředku. Nebo je počáteční hodnota 0 pro blokování přístupu během inicializace aplikace. Po inicializaci se hodnota zvedne na maximální hodnotu.

Vlákna nevlastní semafor. Při opakovaném čekání na semafor se tento vždy dekrementuje a při 0 se vlákno samo blokuje. Zvednout hodnotu semaforu může libovolné vlákno. Dekrementování o více než 1 se provede více voláními čekací funkce s tímto semaforem. Uvedení jednoho semaforu vícekrát v multiple-object čekací funkci ho dekrementuje pouze o 1.

Př. Napište program, ve kterém vytvoříte vlákno. Jedno vlákno bude něco zapisovat do společného bufferu (naplní ho), druhé z něho číst (vyprázdní ho). Synchronizujte tyto dvě vlákna semafory tak, aby se zapisovalo, jen když je buffer prázdný, a četlo, jen když je plný, a do bufferu v danou chvíli přistupovalo jen jedno vlákno.

Mutex (Mutex object)

Mutex je signalizován, když ho nevlastní žádné vlákno, a nesignalizován, když ho vlastní jedno vlákno. Mutex může vlastnit právě jedno vlákno, používá se k exkluzivnímu přístupu ke sdílenému prostředku (MUTtually EXclusive). Např. pro zabránění dvěma vláknům zápisu do sdílené paměti ve stejnou chvíli, obě čekají na vlastnictví objektu a pak jedno zapisuje, potom vlákno mutex uvolní.

 

CreateMutex
  • vytvoří (nebo vrací) mutex se jménem lpName (může být NULL), defaultně práva MUTEX_ALL_ACCESS a nedědí se
  • bInitialOwner určuje okamžité vlastnictví (TRUE)
  • vrací handle objektu, při chybě NULL
  • pro zrušení stačí CloseHandle, při ukončení procesu se zruší automaticky

 

 

OpenMutex
  • vrací handle existujícího mutexu se jménem lpName, při chybě NULL
  • dwDesiredAccess specifikuje přístupová práva objektu, MUTEX_ALL_ACCESS pro všechna
  • bInheritHandle povoluje dědění (TRUE)

 

Vlákno požaduje vlastnictví mutexu pomocí některé čekací funkce. Pokud již mutex vlastní jiné vlákno, čekací funkce vlákno blokuje, dokud to druhé vlákno mutex neuvolní. Když vlákno vlastní mutex, může ho použít v opakovaných voláních čekací funkce bez blokování (vlákno neblokuje samo sebe). Pro uvolnění mutexu ho musí uvolnit stejně počet-krát.

 

ReleaseMutex
  • uvolní vlastnictví mutexu (hMutex), může jen vlákno, které mutex vlastní

 

Př. Nahraďte patřičné semafory z předchozího příkladu mutexy.

Další funkce týkající se čekacích funkcí, objektů události, semaforů nebo mutexů jsou:

  • WaitForSingleObjectEx
  • WaitForMultipleObjectsEx
  • MsgWaitForMultipleObjectsEx
  •