7. domácí úloha: Ricart-Agrawalovo vyloučení #
- Předloha: pdv-07mutex.zip
- Do BRUTE odevzdávejte zip archiv obsahující soubor
ExclusionPrimitive.javaa případně další soubory s vlastními třídami (nesmí přepisovat kód projektu). Pokud využíváte vlastní třídy, musí se nacházet v packageexclusion!
Vzájemné vyloučení (anglicky mutual exclusion, nebo zkráceně mutex) je algoritmus používaný v konkurentním programování jako synchronizační prostředek. V paralelní části předmětu PDV jsme s mutexy pracovali prakticky na každém cvičení. Mutex zabraňuje tomu, aby dvě vlákna (nebo procesy) vykonávala operace nad stejným sdíleným prostředkem, tedy aby současně vstoupila do stejné kritické sekce. V paralelním programování za nás problém vzájemného vyloučení řešil operační systém, který plnil roli centrální autority. Na rozdíl od tohoto centralizovaného řešení se v distribuovaných výpočtech musí všechny procesy shodnout na tom, kdo v dané chvíli bude mutex vlastnit.
V tomto domácím úkolu si vyzkoušíte implementaci jednoho z algoritmů pro vzájemné vyloučení v distribuovaném prostředí. V takovém systému obecně nelze využít prostředků poskytovaných jediným lokálním strojem, řešení musí být od základu postaveno pouze na výměně zpráv. Algoritmus, který budete implementovat, se podle svých tvůrců jmenuje Ricart-Agrawalův a funguje následujícím způsobem:
Každý proces má své lokální skalární logické hodiny, a zámek (či několik zámků), kde každý má
- identifikátor kritické sekce, kterou zamyká,
- stav: RELEASED, HELD nebo WANTED, a
- frontu odložených požadavků.
Na začátku je stav každého zámku každého procesu nastaven na RELEASED. V systému kolují pro každou kritickou sekci dva typy zpráv: REQUEST a OK
- Pokud chce proces
P[i]požádat o vstup do kritické sekceK, zaznamená časT[i]kdy o zdroj žádá a pošle zprávuREQUEST(K)s tímto časem všem procesům, které doKpřistupují. Nastaví stav svého zámkuKna WANTED. - Zámek
Kprocesu je ve stavu WANTED dokud neobdrží zprávuOK(K)od každého dalšího přistupujícího procesu. Poté se nastaví na HELD. - Pokud procesu
P[j]přijde zprávaREQUEST(K)od procesuP[i]s časemT[i], tak- pokud je zámek
Kve stavu HELD, pak zprávuREQUEST(K)zařadí mezi odložené požadavky a neodpoví - pokud je ve stavu WANTED a o vstup do kritické sekce žádal v čase
T[j] < T[i], případněT[j] = T[i]aj < i, pak zprávuREQUEST(K)zařadí mezi odložené požadavky a neodpoví, - jinak pošle zprávu
OK(K)procesuP[i].
- pokud je zámek
- Pokud proces
P[i]dokončí práci v kritické sekciK, nastaví stav zámkuKna RELEASED, odpoví na všechny zprávy ve frontě zámku a frontu vyprázdní.
Vaším úkolem bude doimplementovat třídu exclusion.ExclusionPrimitive, která bude implementovat zámek podle protokolu Ricart-Agrawala. Instanci této třídy vlastní každý proces, který chce přistupovat do kritické sekce se jménem criticalSectionName (seznam všech procesů, které rozhodují o přístupu do kritické sekce naleznete v poli allAccessingProcesses). Proces, který danou instanci ExclusionPrimitive vlastní na ní může volat následující metody:
requestEnter()ve chvíli, kdy chce vstoupit do kritické sekce se jménemcriticalSectionNameisHeld()pro zjištění, zda mu byl už přidělen excluzivní přístup do sekcecriticalSectionNameexit()pro opuštění kritické sekcecriticalSectionName(a uvolnění zdroje pro ostatní procesy)
ExclusionPrimitive zpracovává zprávy pomocí metody accept(). Pokud se zpráva týká kritické sekce criticalSectionName, tak ji zpracujte a vraťte true. V opačném případě vraťte false. Ve Vašem kódu můžete používat metody procesu, který danou instanci ExclusionPrimitive vlastní (objekt owner).
V aplikaci může být (a bude) více kritických sekcí s různýmicriticalSecionName. Při zpracovávání zpráv tak musíte ověřovat, zda se přijatá zpráva skutečně týká kritické sekce, kterou obsluhuje danýExclusionPrimitive!
Abychom Vám usnadnili práci, naimplementovali jsme Vám logiku Lamportových (skalárních) hodin. Tato logika je implementovaná ve třídě clocked.ClockedProcess a zajišťuje, že:
- Po přijetí zprávy dojde k aktualizaci logického času pomocí následujícího pravidla:
currentTime = max(currentTime, msg.sentOn) + 1. - Každá odeslaná zpráva je označena aktuálním logickým časem odeslání:
msg.sentOn = currentTime
Inkrementaci logického času (například před odesláním zprávy) musíte provádět ručně voláním metody increaseTime().
Abyste mohli využítClockedProcess, Vaše zprávy musí dědit od třídyclocked.ClockedMessage(a nikoliv od obecné třídyMessage).
Vaší implemenaci můžete vyzkoušet na scénáři bank.Main. Několik bankovních úředníků (BankOfficerProcess) zpracovává příkazy k převodu mezi bankovními účty. Ve chvíli, kdy dochází ke zpracování příkazu pro převod peněz z účtu account[i] na účet account[j] dojde
- Ke vstupu do kritických sekcí
account[i]aaccount[j](voláním metodyrequestEnterodpovídajícího objektuExclusionPrimitivea následným vyčkáním na splnění podmínkyisHeld() == true). Vzpomeňte si, že jednou z možností, jak lze v případě zamykání více mutexů předejít deadlocku, je zamykat je v přesně daném pořadí – to zajišťuje třídaMultiLock. - K přečtení aktuálních hodnot účtů
iajzasláním zprávyReadMessageprocesuDatastoreProcessa vyčkáním na odpověď (zprávyValueMessage). - K zapsání nových zůstatků účtů
iajzasláním zprávyWriteMessageprocesuDatastoreProcessa vyčkáním na potvrzení (zprávamiWriteAcknowledgedMessage). - K opuštění kritických sekcí
account[i]aaccount[j]voláním metodyexit()na odpovídajících instancíchExclusionPrimitive.