2014-11-11 5 views
6

Ich hoffe, jemand kann mir helfen zu verstehen, warum der folgende Code die Ausgabe unten erzeugt. Der Code stammt aus dem Concurrency-Kapitel in Simon Marlows Buch (Link unten).`forkIO` und` putMVar`: Was passiert unter der Haube?

Basierend auf der Beschreibung der verschiedenen Funktionen ging ich davon aus, dass die zweite putMVar Funktion blockiert werden sollte, da (i) beide putMVar Funktionen Teil desselben Threads sind und (ii) ein Wert bereits zugewiesen wurde. Das ist eindeutig nicht der Fall. Es wäre schön zu verstehen, was hier "unter der Haube" vor sich geht.

. (Anmerkung: das Buch verwendet do Notation, aber ich habe eine Vorliebe für >>= Notation, wie ich es einfacher denken - daher die Version des Codes unten)

Link to book

import Control.Concurrent 

main :: IO() 
main = newEmptyMVar >>= 
     \m -> forkIO (putMVar m 'x' >>= \_ -> putMVar m 'y') >>= 
      \_ -> takeMVar m >>= 
        print >>= 
        \_ -> takeMVar m >>= 
         print 

Ausgabe der obige Code:

% ./mvar2 
'x' 
'y' 

Antwort

10

Um meinetwillen, hier ist der Code in do Notation.

main :: IO() 
main = do 
    m <- newEmptyMVar 
    forkIO $ do 
    putMVar m 'x' 
    putMVar m 'y' 
    x <- takeMVar m 
    print x 
    y <- takeMVar m 
    print y 

Was wir haben, ist ein Hintergrundfaden und der Haupt-Thread gleichzeitig laufenden ein kleines Stück Speicher kommuniziert, die so genannte MVarm.

MVar Semantik sind als solche: ein MVar kann leer oder voll sein. Wenn Sie eine MVar lesen möchten und diese leer ist, müssen Sie warten, bis sie voll ist. Wenn Sie readMVar dann werden Sie einfach den Wert in einem vollen MVar gespeichert, sobald Sie können. Wenn Sie takeMVar dann werden Sie den Wert auflösen und dann sofort leeren es nach dem Lesen.

Auf der anderen Seite, wenn Sie putMVar einen neuen Wert in eine MVar setzen, werden Sie sofort erfolgreich sein, wenn die MVar leer ist. Wenn es voll ist, müssen Sie warten, bis es leer ist.

Da auf den Lese- und Schreibseiten gewartet wird, werden die Threads synchronisiert über die Leere und Fülle der MVar.

In diesem Beispiel können wir uns viele mögliche linearisierte Geschichten vorstellen, wie die Ausführung voranschreitet. Zum Glück arbeiten sie alle identisch. Nennen wir den Hintergrund-Thread BG und den Haupt-Thread MN.

t = 1 : MN makes a new, empty MVar called 'm' 
t = 2 : BG puts 'x' in 'm' making it full 
t = 3 : BG attempts to put 'y' in 'm', but since 'm' is full BG blocks 
t = 4 : MN attempts to read 'm' and succeeds as it is full 
t = 5 : BG now places 'y' into the newly empty 'm' 
t = 6 : BG dies 
t = 6 : MN prints the value it previously read 
t = 7 : MN attempts to read 'm' and succeeds as it is full 
t = 8 : MN prints the value it previously read 
t = 9 : MN dies 

Wie wir sehen können, BG aus setzen mehr Werte in der MVar verhindert als das, was MN lesen kann. Dies erzeugt die gedruckte Semantik, die Sie beobachtet haben.