Mutex ( anglicky mutex , od vzájemného vyloučení - „vzájemné vyloučení“) je synchronizační primitivum, které zajišťuje vzájemné vyloučení provádění kritických částí kódu [1] . Klasický mutex se od binárního semaforu liší přítomností výhradního vlastníka, který jej musí uvolnit (tj. převést do odemčeného stavu) [2] . Mutex se liší od spinlocku předáním řízení plánovači pro přepínání vláken, když mutex nelze získat [3] . Existují také zámky pro čtení a zápis zvané sdílené mutexy, které poskytují kromě výhradního zámku také sdílený zámek, který umožňuje sdílené vlastnictví mutexu, pokud neexistuje žádný výhradní vlastník [4] .
Klasický mutex může být obvykle reprezentován jako proměnná, která může být ve dvou stavech: uzamčeno a odemčeno. Když vlákno vstoupí do své kritické sekce, zavolá funkci k uzamčení mutexu a zablokuje vlákno, dokud není mutex uvolněn, pokud jej již vlastní jiné vlákno. Při opuštění kritické sekce vlákno zavolá funkci pro přesunutí mutexu do odemčeného stavu. Pokud je během odemykání mutexem zablokováno několik vláken, plánovač vybere vlákno, které obnoví provádění (v závislosti na implementaci to může být buď náhodné vlákno, nebo vlákno určené podle některých kritérií) [5] .
Úkolem mutexu je chránit objekt před přístupem z jiných vláken, než je to, které vlastní mutex. V každém okamžiku může pouze jedno vlákno vlastnit objekt chráněný mutexem. Pokud jiné vlákno potřebuje přístup k datům chráněným mutexem, pak se toto vlákno zablokuje , dokud nebude mutex uvolněn. Mutex chrání data před poškozením asynchronními změnami ( spor ), ale při nesprávném použití mohou být způsobeny další problémy, jako je uváznutí nebo dvojité zachycení .
Podle typu implementace může být mutex rychlý, rekurzivnínebo s kontrolou chyb.
Inverze priority nastane, když by se měl provádět proces s vysokou prioritou, ale zamkne se na mutexu vlastněném procesem s nízkou prioritou a musí počkat, dokud proces s nízkou prioritou mutex neodemkne. Klasickým příkladem inverze neomezené priority v systémech pracujících v reálném čase je situace, kdy proces se střední prioritou zabírá čas CPU, v důsledku čehož proces s nízkou prioritou nemůže běžet a nemůže odemknout mutex [6] .
Typickým řešením problému je dědění priority, kdy proces vlastnící mutex zdědí prioritu jiného jím blokovaného procesu, pokud je priorita blokovaného procesu vyšší než priorita aktuálního [6] .
Win32 API ve Windows má dvě implementace mutexů – samotné mutexy, které mají názvy a jsou dostupné pro použití mezi různými procesy [7] , a kritické sekce , které mohou být použity pouze v rámci stejného procesu různými vlákny [8] . Každý z těchto dvou typů mutexů má své vlastní funkce zachycení a uvolnění [9] . Kritická část ve Windows je o něco rychlejší a efektivnější než mutex a semafor, protože používá instrukci test-and-set specifickou pro procesor [8] .
Balíček Pthreads poskytuje různé funkce, které lze použít k synchronizaci vláken [10] . Mezi těmito funkcemi jsou funkce pro práci s mutexy. Kromě funkcí získání a uvolnění mutexu je k dispozici funkce pokusu o získání mutexu, která vrátí chybu, pokud se očekává zablokování vlákna. Tuto funkci lze v případě potřeby použít v aktivní čekací smyčce [11] .
Funkce balíku Pthreads pro práci s mutexyFunkce | Popis |
---|---|
pthread_mutex_init() | Vytvoření mutexu [11] . |
pthread_mutex_destroy() | Destrukce mutexu [11] . |
pthread_mutex_lock() | Přenos mutexu do uzamčeného stavu (zachycení mutexu) [11] . |
pthread_mutex_trylock() | Pokuste se uvést mutex do blokovaného stavu a vrátit chybu, pokud by se vlákno mělo zablokovat, protože mutex již má vlastníka [11] . |
pthread_mutex_timedlock() | Pokuste se přesunout mutex do uzamčeného stavu a vrátit chybu, pokud se pokus nezdaří před určeným časem [12] . |
pthread_mutex_unlock() | Převedení mutexu do odemčeného stavu (uvolnění mutexu) [11] . |
Pro řešení specializovaných problémů lze mutexům přiřadit různé atributy [11] . Prostřednictvím atributů lze pomocí funkce pthread_mutexattr_settype()nastavit typ mutexu, který ovlivní chování funkcí pro zachycení a uvolnění mutexu [13] . Mutex může být jeden ze tří typů [13] :
Standard C17 programovacího jazyka C definuje typ mtx_t[15] a sadu funkcí pro práci s ním [16] , které musí být dostupné, pokud makro __STDC_NO_THREADS__nebylo definováno překladačem [15] . Sémantika a vlastnosti mutexů jsou obecně v souladu se standardem POSIX.
Typ mutexu je určen předáním kombinace příznaků funkci mtx_init()[17] :
Možnost použití mutexů prostřednictvím sdílené paměti různými procesy není ve standardu C17 uvažována.
Standard C++17 programovacího jazyka C++ definuje 6 různých tříd mutexů [20] :
Knihovna Boost navíc poskytuje pojmenované a meziprocesové mutexy a také sdílené mutexy, které umožňují získání mutexu pro sdílené vlastnictví více vlákny dat pouze pro čtení bez vyloučení zápisu po dobu pořízení zámku, což je v podstatě mechanismus pro zámky čtení a zápisu [25] .
V obecném případě mutex ukládá nejen svůj stav, ale také seznam blokovaných úloh. Změnu stavu mutexu lze implementovat pomocí atomických operací závislých na architektuře na úrovni uživatelského kódu, ale po odemknutí mutexu musí být obnoveny i další úlohy, které byly mutexem zablokovány. Pro tyto účely se dobře hodí synchronizační primitivum nižší úrovně - futex , který je implementován na straně operačního systému a přebírá funkcionalitu blokovacích a odblokovacích úloh, umožňujících mimo jiné vytvářet meziprocesové mutexy [26] . Konkrétně pomocí futexu je mutex implementován v balíčku Pthreads v mnoha distribucích Linuxu [ 27] .
Jednoduchost mutexů umožňuje jejich implementaci v uživatelském prostoru pomocí instrukce assembleru XCHG, která dokáže atomicky zkopírovat hodnotu mutexu do registru a současně nastavit hodnotu mutexu na 1 (dříve zapsané do stejného registru). Hodnota mutexu nula znamená, že je v uzamčeném stavu, zatímco hodnota jedna znamená, že je v odemčeném stavu. Hodnotu z registru lze otestovat na 0 a v případě nulové hodnoty je třeba vrátit řízení do programu, což znamená, že mutex je získán, pokud byla hodnota nenulová, pak je třeba přenést řízení na plánovač obnovit práci jiného vlákna, následovaný druhým pokusem o získání mutexu, který slouží jako analog aktivního blokování. Mutex se odemkne uložením hodnoty 0 do mutexu pomocí příkazu XCHG[28] . Alternativně lze použít LOCK BTS(implementace TSL pro jeden bit) nebo CMPXCHG[29] ( implementace CAS ).
Přenos řízení na plánovač je dostatečně rychlý, takže neexistuje žádná skutečná aktivní smyčka čekání, protože CPU bude zaneprázdněno prováděním dalšího vlákna a nebude nečinné. Práce v uživatelském prostoru vám umožňuje vyhnout se systémovým voláním, která jsou nákladná z hlediska času procesoru [30] .
Architektura ARMv7 využívá k synchronizaci paměti mezi procesory tzv. lokální a globální exkluzivní monitory, což jsou stavové stroje, které řídí atomový přístup k paměťovým buňkám [31] [32] . Atomické čtení paměťové buňky lze provést pomocí instrukce LDREX[33] a atomický zápis lze provést pomocí instrukce STREX, která také vrátí příznak úspěchu operace [34] .
Algoritmus zachycení mutexu zahrnuje čtení jeho hodnoty LDREXa kontrolu načtené hodnoty pro uzamčený stav, který odpovídá hodnotě 1 proměnné mutex. Pokud je mutex uzamčen, je volán čekající kód na uvolnění zámku. Pokud byl mutex v odemčeném stavu, pak by se mohl pokusit o zámek pomocí instrukce write-exclusive STREXNE. Pokud se zápis nezdaří, protože se změnila hodnota mutexu, pak se algoritmus zachycení opakuje od začátku [35] . Po zachycení mutexu se provede instrukce DMB, která zaručí integritu paměti zdroje chráněného mutexem [36] .
Před uvolněním mutexu se také zavolá instrukce DMB, po které se pomocí instrukce zapíše do proměnné mutex hodnota 0 STR, což znamená převedení do odemčeného stavu. Po odemknutí mutexu by měly být čekající úkoly, pokud existují, signalizovány, že mutex byl uvolněn [35] .
Meziprocesová komunikace | |
---|---|
Metody | |
Vybrané protokoly a standardy |