Tråd synkronisering med mutex

Tags:    c++
Skrevet af Bruger #2695 @ 09.03.2004

Indledning


Hej igen.
I denne artikel vil jeg bygge lidt videre på projektet, som jeg startede i artiklen om multithreading. I den artikel nævnte jeg, at trådes adgang til resourcer kan komme ret ubelejligt, og vi så endda et eksempel på det. Jeg vil starte med at forstærke effekten ved at skrive et multitrådet program, som crasher, fordi to tråde bruger samme resourcer på en usmart måde. Så vil vi prøve at synkronisere trådene bagefter, så samme program ikke længere har problemer.
Alle operativsystemer har flere forskellige metoder til at synkronisere tråde, og den, vi skal se på i denne artikel, hedder mutex (Mutual Exclusion: Gensidig udelukkelse).

Kritisk sektion


Før vi går i gang, skal vi lige se på, hvad problemet er. Forestil jer følgende kode:
Fold kodeboks ind/udKode 

Hvis enPointerTilEtObjekt peger på noget, så kalder vi en funktion på den. Det ser udemærket ud, men hvis programmet, som denne kode er en del af, er multitrådet, kan der komme problemer.
Forestil dig at følgende bliver udført af tråd 1:
Fold kodeboks ind/udKode 

Derefter skiftes der til tråd 2 inden funktionen på enPointerTilEtObjekt bliver udført. Tråd 2 kører følgende kode:
Fold kodeboks ind/udKode 

..og derefter skiftes der igen. Nu peger enPointerTilEtObjekt ikke længere på noget, men det tror tråd 1, at den gør og fortsætter:
Fold kodeboks ind/udKode 

---CRASH---
Adgangen til en delt resource kaldes en kritisk sektion, hvis kun én tråd af gangen må tilgå resourcen, og man skal derfor sørge for, at kun én tråd er i en kritisk sektion. Dette kan gøres med mutex objekter.

Et program der crahser


Vi starter med at kigge på et helt program, der crasher. Så vil vi senere synkronisere det, så det ikke længere crasher.
Her er koden:
Fold kodeboks ind/udKode 

Tråd 1 tjekker om resourcen g_integer er allokeret, men først to sekunder efter, bruges den. Det giver tråd 2 masser af tid til at slette g_integer, og tråd 1 vil derfor crashe programmet.
Koden giver følgende under Linux:
Fold kodeboks ind/udKode 

Og dette under Windows:
Fold kodeboks ind/udKode 

Desuden får jeg følgende skærmbillede under Windows:


Et program der ikke crasher


Disse problemer er faktisk ret nemme at komme udenom. Linux har følgende funktioner:
Fold kodeboks ind/udKode 

pthread_mutex_init initialiserer en mutex.
pthread_mutex_destroy nedlægger en mutex.
pthread_mutex_lock tjekker, om en mutex er låst. Hvis den er låst, så lægger den kaldende tråd sig til at sove, til mutexen bliver låst op, hvorefter den låser mutexen og returnerer. Hvis mutexen ikke er låst, låses den, og funktionen returnerer.
pthread_mutex_trylock virker næsten ligesom pthread_mutex_lock, bortset fra, at tråden ikke sover hvis, mutexen allerede er låst. Funktionen returnerer en boolsk værdi, som indikerer, om den kaldende tråd låste mutexen eller ej.
pthread_mutex_unlock låser en mutex op og signalerer evt. ventende tråde om, at mutexen er fri.

Windows har det samme sæt af funktioner med samme funktionalitet. De hedder bare noget andet:
Fold kodeboks ind/udKode 


Man starter med at oprette en mutex med pthread_mutex_init/InitializeCriticalSection for hver delt resource, som kan skabe problemer. Denne mutex er delt mellem alle tråde. Derefter skal man låse mutexen med pthread_mutex_lock/EnterCriticalSection eller pthread_mutex_trylock/TryEnterCriticalSection, hver gang man vil tilgå resourcen. Det bevirker, at kun én tråd ad gangen kan komme til at bruge resourcen. Når en tråd er færdig med resourcen, låses mutexen op med pthread_mutex_unlock/LeaveCriticalSection.
Når programmet afsluttes, eller resourcen ikke længere er tilgængelig, frigives mutexen med pthread_mutex_destroy/DeleteCriticalSection.

Nu, hvor vi ved, hvordan man beskytter sin kritiske sektion, skriver vi lige programmet fra før om, så det ikke længere crasher:

Fold kodeboks ind/udKode 


Simpelt nok. Vi kører det under Linux:
Fold kodeboks ind/udKode 

...og under Windows:
Fold kodeboks ind/udKode 

Meget bedre.

En Mutex klasse


Vi er som sædvanlig ikke tilfredse med en masse grim preprocessor kode over det hele, så vi pakker funktionaliteten ind i en klasse, som jeg har valgt at kalde Mutex:
Fold kodeboks ind/udKode 


Implementeringen er ikke så svær:
Fold kodeboks ind/udKode 


Et revideret program der ikke crasher


Det var det!!
Vi slutter selvfølgelig af med at lægge vores nye klasse ind i vores program, så vi kan se den i aktion:
Fold kodeboks ind/udKode 


Konklusion


Mutexes er én af mange måder at beskytte kritiske sektioner på. Den er god, når kun én tråd må bruge en resource, og det er det, jeg selv oftest har haft brug for. En anden måde at synkronisere tråde på er med semaforer. Det vil jeg måske skrive en artikel om senere. Indtil da..håber du lærte noget.



Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (3)

User
Bruger #4841 @ 15.04.04 14:37
Udmærket artikel.
Jeg har dog en kommentar til din mutex klasse.
Et vigtigt idiom i c++ programmering er RAII (søg på Google). Det ville i dette tilfælde betyde at man ikke benytter enter/leave funktioner men i stedet lader constructor/destructorer ordne det. På den måde behøver man blot at erklære en instans af objektet når man vil låse det, og man behøver ikke skrive noget som helst for at låse det op.
Det har den fordel at man får sine resourcer (i dette tilfælde mutex'en) frigivet uanset om man har indre return statements som man havde glemt - eller hvis der smides exceptions.
User
Bruger #2695 @ 16.06.04 14:28
Det kan du have ret i. Jeg vil tilføje et afsnit, som beskriver, hvordan man kan bruge constructor/destructor til at låse/åbne sin mutex.
User
Bruger #13410 @ 22.09.08 09:54
:bounce::bounce::bounce::bounce::bounce::bounce::bounce::bounce:
Du skal være logget ind for at skrive en kommentar.
t