Mutex ( engelska mutex , från ömsesidig uteslutning - "ömsesidig uteslutning") är en synkroniseringsprimitiv som ger ömsesidig uteslutning av exekvering av kritiska avsnitt av kod [1] . En klassisk mutex skiljer sig från en binär semafor genom närvaron av en exklusiv ägare, som måste släppa den (dvs överföra den till ett olåst tillstånd) [2] . En mutex skiljer sig från en spinlock genom att skicka kontrollen till schemaläggaren för att byta trådar när mutex inte kan erhållas [3] . Det finns även läs- och skrivlås som kallas delade mutex som tillhandahåller, förutom det exklusiva låset, ett delat lås som tillåter delat ägande av mutexen om det inte finns någon exklusiv ägare [4] .
Konventionellt kan en klassisk mutex representeras som en variabel som kan vara i två tillstånd: låst och olåst. När en tråd går in i dess kritiska sektion anropar den en funktion för att låsa mutex, blockerar tråden tills mutex släpps om en annan tråd redan äger den. När du lämnar den kritiska sektionen anropar tråden funktionen för att flytta mutexen till det olåsta tillståndet. Om det finns flera trådar blockerade av en mutex under upplåsning, väljer schemaläggaren en tråd för att återuppta exekveringen (beroende på implementeringen kan detta antingen vara en slumpmässig tråd eller en tråd som bestäms av vissa kriterier) [5] .
En mutexs uppgift är att skydda objektet från att nås av andra trådar än den som äger mutexen. Vid varje givet ögonblick kan bara en tråd äga ett objekt skyddat av en mutex. Om en annan tråd behöver åtkomst till data som skyddas av mutex, kommer den tråden att blockeras tills mutex släpps. En mutex skyddar data från att skadas av asynkrona förändringar ( ett rastillstånd ), men andra problem som dödläge eller dubbelfångst kan orsakas om de används på fel sätt .
Efter typ av implementering kan mutex vara snabb, rekursiveller med felkontroll.
En prioritetsinvertering inträffar när en process med hög prioritet ska köras, men den låser på en mutex som ägs av den lågprioriterade processen och måste vänta tills den lågprioriterade processen låser upp mutexen. Ett klassiskt exempel på obegränsad prioritetsinversion i realtidssystem är när en process med medelprioritet tar CPU-tid, vilket gör att processen med låg prioritet inte kan köras och inte kan låsa upp mutexen [6] .
En typisk lösning på problemet är prioritetsarv, där en process som äger en mutex ärver prioriteten för en annan process som blockeras av den, om prioriteten för den blockerade processen är högre än den för den nuvarande [6] .
Win32 API i Windows har två implementeringar av mutexes - mutexes själva, som har namn och är tillgängliga för användning mellan olika processer [7] , och kritiska sektioner , som endast kan användas inom samma process av olika trådar [8] . Var och en av dessa två typer av mutexer har sina egna fångst- och släppfunktioner [9] . Den kritiska delen av Windows är något snabbare och mer effektiv än mutex och semafor eftersom den använder den processorspecifika test-and-set- instruktionen [8] .
Pthreads - paketet tillhandahåller olika funktioner som kan användas för att synkronisera trådar [10] . Bland dessa funktioner finns funktioner för att arbeta med mutexes. Förutom mutex-förvärvnings- och frigöringsfunktionerna tillhandahålls en mutex-förvärvningsförsöksfunktion som returnerar ett fel om en trådblockering förväntas. Denna funktion kan användas i en aktiv vänteloop om behov uppstår [11] .
Fungera | Beskrivning |
---|---|
pthread_mutex_init() | Skapa en mutex [11] . |
pthread_mutex_destroy() | Mutex-destruktion [11] . |
pthread_mutex_lock() | Överföra en mutex till ett låst tillstånd (mutex capture) [11] . |
pthread_mutex_trylock() | Försök att sätta mutexet i blockerat tillstånd och returnera ett felmeddelande om tråden skulle blockera eftersom mutexen redan har en ägare [11] . |
pthread_mutex_timedlock() | Försök att flytta mutexet till det låsta tillståndet och returnera ett fel om försöket misslyckades före den angivna tiden [12] . |
pthread_mutex_unlock() | Överföra mutex till olåst tillstånd (släpp av mutex) [11] . |
För att lösa specialiserade problem kan mutexes tilldelas olika attribut [11] . Genom attribut, med funktionen pthread_mutexattr_settype(), kan du ställa in typen av mutex, vilket kommer att påverka beteendet hos funktionerna för att fånga och släppa mutexet [13] . En mutex kan vara en av tre typer [13] :
C17 -standarden för programmeringsspråket C definierar en typ mtx_t[15] och en uppsättning funktioner som ska fungera med den [16] som måste vara tillgängliga om makrot __STDC_NO_THREADS__inte har definierats av kompilatorn [15] . Semantiken och egenskaperna för mutexes överensstämmer i allmänhet med POSIX-standarden.
Mutex-typen bestäms genom att skicka en kombination av flaggor till funktionen mtx_init()[17] :
Möjligheten att använda mutexes genom delat minne av olika processer beaktas inte i C17-standarden.
C ++ 17-standarden för programmeringsspråket C++ definierar 6 olika mutex-klasser [20] :
Boost -biblioteket tillhandahåller dessutom namngivna mutexer och mutexer mellan processer, såväl som delade mutexer, som tillåter förvärv av en mutex för delat ägande av flera trådar av skrivskyddad data utan skrivexkludering under varaktigheten av låsförvärvet, vilket är i huvudsak en mekanism för läs- och skrivlås [25] .
I det allmänna fallet lagrar mutex inte bara dess tillstånd, utan också en lista över blockerade uppgifter. Ändring av tillståndet för en mutex kan implementeras med hjälp av arkitekturberoende atomoperationer på användarkodnivå, men vid upplåsning av mutexen måste även andra uppgifter som blockerades av mutexen återupptas. För dessa ändamål är en synkroniseringsprimitiv på lägre nivå väl lämpad - futex , som implementeras på sidan av operativsystemet och tar på sig funktionaliteten av att blockera och avblockera uppgifter, vilket bland annat tillåter att skapa mutexes mellan processer [26] . I synnerhet, med hjälp av futex, implementeras mutex i Pthreads- paketet i många Linux- distributioner [27] .
Enkelheten med mutex gör att de kan implementeras i användarutrymmet med hjälp av en assemblerinstruktion XCHGsom kan atomiskt kopiera mutexens värde till ett register och samtidigt ställa in mutexens värde till 1 (som tidigare skrivits till samma register). Ett mutex-värde på noll betyder att det är i låst tillstånd, medan ett värde på ett betyder att det är i olåst tillstånd. Värdet från registret kan testas för 0, och vid ett nollvärde ska kontroll återföras till programmet, vilket innebär att mutexet förvärvas, om värdet inte var noll så måste styrningen överföras till schemaläggaren för att återuppta arbetet med en annan tråd, följt av ett andra försök att förvärva mutex, som fungerar som en analog av aktiv blockering. En mutex låses upp genom att lagra värdet 0 i mutexet med kommandot XCHG[28] . Alternativt kan LOCK BTS(TSL-implementering för en bit) eller CMPXCHG[29] ( CAS- implementering ) användas.
Överföringen av kontroll till schemaläggaren är tillräckligt snabb för att det inte finns någon verklig aktiv vänteloop, eftersom CPU:n kommer att vara upptagen med att köra en annan tråd och inte kommer att vara ledig. Genom att arbeta i användarutrymme kan du undvika systemsamtal som är dyra i termer av processortid [30] .
ARMv7 - arkitekturen använder så kallade lokala och globala exklusiva monitorer för att synkronisera minnet mellan processorer, som är tillståndsmaskiner som styr atomaccess till minnesceller [31] [32] . En atomär läsning av en minnescell kan utföras med hjälp av instruktionen LDREX[33] , och en atomär skrivning kan göras genom instruktionen STREX, som också returnerar framgångsflaggan för operationen [34] .
Algoritmen för mutexinfångning innebär att man läser dess värde med LDREXoch kontrollerar det avlästa värdet för ett låst tillstånd, vilket motsvarar värdet 1 för mutexvariabeln. Om mutex är låst anropas koden för låsfrigöring. Om mutexen var i olåst tillstånd, kunde låsningen försökas med den skrivexklusiva instruktionen STREXNE. Om skrivningen misslyckas på grund av att mutexens värde har ändrats, upprepas infångningsalgoritmen från början [35] . Efter att ha fångat mutexet exekveras instruktionen DMB, vilket garanterar integriteten hos minnet för den resurs som skyddas av mutexen [36] .
Innan mutex släpps kallas instruktionen även DMB, varefter värdet 0 skrivs till mutex-variabeln med hjälp av instruktionen STR, vilket innebär överföring till olåst tillstånd. Efter att mutex har låsts upp, bör väntande uppgifter, om några, signaleras att mutex har släppts [35] .
Kommunikation mellan processer | |
---|---|
Metoder | |
Utvalda protokoll och standarder |