A Mutex ( angol mutex , kölcsönös kizárásból - „kölcsönös kizárás”) egy szinkronizálási primitív, amely biztosítja a kód kritikus szakaszainak végrehajtásának kölcsönös kizárását [1] . A klasszikus mutex abban különbözik a bináris szemafortól , hogy kizárólagos tulajdonosa van, akinek el kell engednie (vagyis zárolatlan állapotba kell vinnie) [2] . A mutex abban különbözik a spinlocktól , hogy a vezérlést az ütemezőnek adja át a szálváltáshoz, amikor a mutex nem szerezhető [3] . Léteznek megosztott mutexnek nevezett olvasási-írási zárak is, amelyek az exkluzív záron kívül olyan megosztott zárat is biztosítanak, amely lehetővé teszi a mutex megosztott tulajdonjogát, ha nincs kizárólagos tulajdonos [4] .
Hagyományosan egy klasszikus mutex változóként ábrázolható, amely két állapotú lehet: zárt és feloldott. Amikor egy szál belép a kritikus szakaszába, meghív egy függvényt a mutex zárolására, és blokkolja a szálat, amíg a mutex fel nem szabadul, ha már egy másik szál birtokolja. A kritikus szakaszból való kilépéskor a szál meghívja a függvényt, hogy a mutexet zárolatlan állapotba helyezze. Ha a feloldás során egy mutex több szálat blokkol, az ütemező kiválaszt egy szálat a végrehajtás folytatásához (az implementációtól függően ez lehet véletlenszerű szál vagy valamilyen kritérium által meghatározott szál) [5] .
A mutex feladata, hogy megvédje az objektumot attól, hogy más szálak hozzáférjenek, mint ami a mutex tulajdonosa. Egy adott pillanatban csak egy szál birtokolhat egy mutex által védett objektumot. Ha egy másik szálnak hozzá kell férnie a mutex által védett adatokhoz, akkor az a szál blokkolva lesz, amíg a mutex fel nem szabadul. A mutex megvédi az adatokat az aszinkron változások miatti sérüléstől ( versenyhelyzet ), de helytelen használat esetén más problémák is előfordulhatnak, például holtpont vagy kettős rögzítés .
A megvalósítás típusa szerint a mutex lehet gyors, rekurzívvagy hibakezeléssel.
A prioritás inverziója akkor történik, amikor egy magas prioritású folyamatnak kell végrehajtania, de az alacsony prioritású folyamat tulajdonában lévő mutexre zárol, és meg kell várnia, amíg az alacsony prioritású folyamat feloldja a mutexet. A korlátlan prioritás-inverzió klasszikus példája a valós idejű rendszerekben, amikor egy közepes prioritású folyamat lefoglalja a CPU-időt, aminek következtében az alacsony prioritású folyamat nem tud futni, és nem tudja feloldani a mutexet [6] .
A probléma tipikus megoldása a prioritás öröklődés, amelyben egy mutex-et birtokló folyamat egy másik általa blokkolt folyamat prioritását örökli, ha a blokkolt folyamat prioritása magasabb, mint az aktuálisé [6] .
A Windows Win32 API-ja két mutexe-megvalósítással rendelkezik - maguk a mutexek, amelyeknek neve van, és különböző folyamatok között használhatók [7] , valamint kritikus szakaszok , amelyeket csak ugyanazon a folyamaton belül használhatnak különböző szálak [8] . A mutexek mindkét típusának megvan a maga rögzítési és feloldási funkciója [9] . A Windows kritikus szakasza valamivel gyorsabb és hatékonyabb, mint a mutex és a szemafor, mivel a processzor-specifikus teszt és beállítás utasítást használja [8] .
A Pthreads csomag különféle funkciókat biztosít a szálak szinkronizálására [10] . Ezen funkciók között vannak mutexekkel való munkavégzésre szolgáló funkciók. A mutex beolvasási és feloldási függvények mellett rendelkezésre áll egy mutex adatszerzési kísérlet funkció, amely hibát ad vissza, ha szálblokkolás várható. Ez a funkció aktív várakozási ciklusban használható, ha szükséges [11] .
Funkció | Leírás |
---|---|
pthread_mutex_init() | Mutex létrehozása [11] . |
pthread_mutex_destroy() | Mutex pusztítás [11] . |
pthread_mutex_lock() | Mutex átvitele zárolt állapotba (mutex rögzítés) [11] . |
pthread_mutex_trylock() | Próbálja meg a mutexet blokkolt állapotba helyezni, és hibaüzenetet ad vissza, ha a szálnak blokkolnia kell, mert a mutexnek már van tulajdonosa [11] . |
pthread_mutex_timedlock() | Próbálja meg a mutexet zárolt állapotba helyezni, és hibaüzenetet ad vissza, ha a kísérlet a megadott idő előtt meghiúsult [12] . |
pthread_mutex_unlock() | A mutex átvitele zárolatlan állapotba (a mutex feloldása) [11] . |
Speciális problémák megoldására a mutexekhez különféle attribútumok rendelhetők [11] . Az attribútumokon keresztül a függvény pthread_mutexattr_settype()segítségével beállíthatja a mutex típusát, amely befolyásolja a mutex rögzítésére és felszabadítására szolgáló függvények viselkedését [13] . A mutex három típus egyike lehet [13] :
A C programozási nyelv C17 szabványa definiál egy típust mtx_t[15] és a vele együtt használható függvénykészletet [16] , amelynek elérhetőnek kell lennie, ha a makrót __STDC_NO_THREADS__nem definiálta a fordító [15] . A mutexek szemantikája és tulajdonságai általában összhangban vannak a POSIX szabvánnyal.
A mutex típusát úgy határozzuk meg, hogy egy jelzőkombinációt adunk át a mtx_init()[17] függvénynek :
A C17 szabvány nem veszi figyelembe a mutexek osztott memórián keresztüli használatának lehetőségét a különböző folyamatokban.
A C++ programozási nyelv C++17 szabványa 6 különböző mutex osztályt határoz meg [20] :
A Boost könyvtár ezenkívül névvel ellátott és folyamatközi mutexet, valamint megosztott mutexet is biztosít, amelyek lehetővé teszik, hogy több, csak olvasható adatszálon keresztül megosztott tulajdonjogot biztosító mutexet szerezzenek írási kizárás nélkül a zárolás beszerzésének idejére. lényegében egy olvasási-írási zárolási mechanizmus [25] .
A mutex általában nem csak az állapotát tárolja, hanem a blokkolt feladatok listáját is. A mutex állapotának megváltoztatása architektúrától függő atomműveletekkel valósítható meg felhasználói kód szinten, de a mutex feloldásakor a mutex által blokkolt egyéb feladatokat is folytatni kell. Ezekre a célokra alkalmas egy alacsonyabb szintű szinkronizálási primitív - a futex , amely az operációs rendszer oldalán van implementálva, és átveszi a blokkoló és feloldó feladatokat, lehetővé téve többek között folyamatközi mutexek létrehozását [26] . A futex használatával a mutex a Pthreads csomagban valósul meg számos Linux disztribúcióban [27] .
A mutexek egyszerűsége lehetővé teszi, hogy a felhasználói térben olyan assembler utasítással valósíthatók meg, XCHGamely képes a mutex értékét atomosan egy regiszterbe másolni, és ezzel egyidejűleg a mutex értékét 1-re állítani (korábban ugyanabba a regiszterbe írták). A nulla mutex érték azt jelenti, hogy zárolt állapotban van, míg az egyes érték azt, hogy zárolatlan állapotban van. A regiszterből származó érték 0-ra tesztelhető, és nulla érték esetén a vezérlést vissza kell adni a programnak, ami azt jelenti, hogy a mutex megtörténik, ha az érték nem nulla volt, akkor a vezérlést át kell adni a programnak. az ütemező egy másik szál munkájának folytatására, majd egy második kísérlet a mutex megszerzésére, amely az aktív blokkolás analógjaként szolgál. A mutex feloldása a 0 érték eltárolásával történik a mutexben a XCHG[28] paranccsal . Alternatív megoldásként LOCK BTS(TSL implementáció egy bitre) vagy CMPXCHG[29] ( CAS implementáció ) használható.
A vezérlés átadása az ütemezőnek elég gyors ahhoz, hogy ne legyen tényleges aktív várakozási hurok, mivel a CPU egy másik szál végrehajtásával lesz elfoglalva, és nem lesz tétlen. A felhasználói térben végzett munka lehetővé teszi a processzoridő szempontjából költséges rendszerhívások elkerülését [30] .
Az ARMv7 architektúra úgynevezett lokális és globális exkluzív monitorokat használ a memória szinkronizálására a processzorok között, amelyek állapotgépek, amelyek vezérlik a memóriacellákhoz való atomi hozzáférést [31] [32] . LDREXA [33] utasítás segítségével egy memóriacella atomi olvasása, az utasításon keresztül pedig egy atomi írás végezhető STREX, amely egyben a művelet sikerjelzőjét is visszaadja [34] .
A mutex rögzítési algoritmus magában foglalja az érték beolvasását LDREXés az olvasási érték ellenőrzését egy zárolt állapothoz, amely megfelel a mutex változó 1-es értékének. Ha a mutex zárolva van, a zár feloldására váró kód meghívódik. Ha a mutex feloldott állapotban volt, akkor a zárolást az íráskizáró utasítással lehet megkísérelni STREXNE. Ha az írás azért nem sikerül, mert a mutex értéke megváltozott, akkor a rögzítési algoritmus elölről megismétlődik [35] . A mutex rögzítése után az utasítás végrehajtásra kerül DMB, amely garantálja a mutex által védett erőforrás memóriájának integritását [36] .
A mutex feloldása előtt az utasítást is meghívják DMB, ami után a mutex változóba a 0 értéket írjuk az utasítás segítségével STR, ami a zárolatlan állapotba átvitelt jelenti. A mutex feloldása után a várakozó feladatokat, ha vannak, jelezni kell, hogy a mutex feloldásra került [35] .
Folyamatközi kommunikáció | |
---|---|
Mód | |
Válogatott protokollok és szabványok |