Weiter Zurück Inhalt

3. Standardabstraktionen

Anhand der im vorigen Abschnitt besprochenen Grundlagen zur MVar lassen sich diverse Standardabstraktionen konstruieren. Dazu gehört die Semaphore, die allerdings von der MVar direkt implementiert wird. Weitere Beispiele für Standardabstraktionen werden im folgenden konstruiert und erläutert.

3.1 Puffer

Bei der Lösung des Erzeuger-Verbraucher-Problems wird eine MVar herangezogen, in die der Produzent seine Daten schreibt und der Konsument Daten entnimmt. Um zu Verhindern, dass der Produzent einen weiteren Wert in die MVar schreibt, ohne das der Empfänger den ersten ausgelesen hat, wird eine zweite MVar benutzt, die Produzent und Konsument synchronisiert.

Abbildung: Datenfluss beim Erzeuger-Verbraucher-Problem

Die resultierende Buffervariable wird als CVar (channel variable) bezeichnet und wird wie folgt konstruiert:

type CVar a = (MVar a,  -- Produzent -> Konsument
               MVar ()) -- Konsument -> Produzent

Die bereitgestellten Operationen auf MVars werden wie folgt konstruiert:

newCVar :: IO (CVar a) 
newCVar 
  = do
    data_var <- newMVar        
    ack_var <- newMVar
    putMVar ack_var ()  
    return (data_var,ack_var)

Erzeugt einen neue CVar, in dem zwei MVars erzeugt werden. Damit ein Wert mit putCVar in den Puffer geschrieben werden kann, wird in ack_var zunächst ein Wert (leeres Paar) hineingeschrieben.

putCVar :: CVar a -> a -> IO () 
putCVar (data_var,ack_var) val
  = do
    takeMVar ack_var
    putMVar data_var val

Schreibt den übergebenen Wert val in den Puffer. Dazu muss der Puffer jedoch leer sein. Steht im Puffer noch ein Wert, so blockiert der Prozess, der putCVar aufruft bei takeMVar ack_var.

getCVar :: CVar a -> IO a 
getCVar (data_var,ack_var)
  = do
    val <- takeMVar data_var 
    putMVar ack_var ()    
    return val

Holt den Wert aus dem Puffer. Ist kein Wert im Puffer, so wird bei Aufruf von takeMVar data_var der Prozess blockiert.

3.2 Kanal mit unbegrenzter Kapazität

Die vorgestellte CVar kann lediglich einen einzigen Wert aufnehmen. Ein gebufferter Kanal hingegen kann unbegrenzt viele Werte aufnehmen. Realisiert wird das Konzept wieder mit Hilfe von MVars.

Der Kanal selbst wird repräsentiert durchdurch zwei MVars, die sicherstellen, dass Schreib- und Leseoperationen, die den Zustand des Kanals verändern, synchronisiert erfolgen. Sie bezeichnen das Ende der Leseposition und das Ende der Schreibposition.

Die Daten in einem Kanal werden durch den Datentyp Stream repräsentiert, der leer ist oder ein Item aufnehmen kann.

Ein Item wiederum ist ein Paar von Streams, von denen der erste das Datenelement und der zweite den Rest der Daten enthält.

Kanal
Abbildung: Ein Kanal mit unbegrenzter Kapazität

Der resultierende Kanal besteht also abwechselnd aus Streams und Items, die wiederum Daten enthalten oder leer sind. Die Zusammenhänge sind in der obigen Abbildung nocheinmal dargestellt. Die Typdeklarationen lauten wie folgt:

type Channel a = (MVar (Stream a),  --Lesen
                  MVar (Stream a))  --Schreiben
				  
type Stream a = MVar (Item a)

type Item a = Item a (Stream a)

Um einen neuen Kanal zu erzeugen, müssen zwei MVars für das Lese- und Schreibende sowie eine leere MVar für den Stream selbst erzeugt werden, der zunächst leer ist.

newChan :: IO (Channel a)
newChan 
  = newMVar             >>= \read ->
    newMVar             >>= \write ->
    newMVar             >>= \hole ->
    putMVar read hole   >>
    putMVar write hole  >>
    return (read,write) 

Um in den Kanal zu schreiben, muss zunächst eine neuer Stream erzeugt werden, die zum neuen Schreibende wird. Anschließend wird ein Item in die alte Schreibposition geschrieben(old_hole).

	  
putChan :: Channel a -> a -> IO ()
putChan (read,write) val
  = newMVar                   >>= \new_hole ->
    takeMVar write            >>= \write ->
    putMVar write new_hole    >> 
    putMVar old_hole (Item val new_hole)

Um Daten aus dem Buffer zu lesen, ist das Vorgehen sehr ähnlich. Dazu wird der Item aus der alten Leseposition geholt und der Wert ausgelesen. Die Leseposition wird anschließend um eine Position vorgerückt. Sind keine Daten im Kanal, so wird der Prozess so lange beim Holen des Items blockiert, bis ein Wert in den Kanal hineingeschrieben wurde.

		
getChan :: Channel a -> IO a
getChan (read,write)
  = takeMVar read             >>= \cts ->
    takeMVar cts              >>= \(Item val new) ->
    putMVar read new          >>
    return val	


Weiter Zurück Inhalt