Einführung in Haskell I/O in Haskell Concurrent Haskell Übersicht Aktuelle Themen zu Informatik der Systeme: 1 Eine kurze Einführung in Haskell 2 I/O in Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze 3 Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Nebenläufige Programmierung: Praxis und Semantik Nebenläufige Programmierung in Haskell (1) PD Dr. David Sabel WS 2013/14 Stand der Folien: 21. Januar 2014 1 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Funktionale Programmiersprachen Einführung in Haskell I/O in Haskell Concurrent Haskell Funktionale Programmiersprachen (2) Ideen der funktionalen Programmiersprachen: Programme bestehen nur aus Funktionen (keine Prozeduren, Methoden, usw.) Seiteneffekte verboten: Funktionen angewendet auf Argumente produzieren einen Wert, dabei gibt es keine (sichtbare) Speicheränderungen oder ähnlich Ersetze imperative Konstrukte (vor allem Schleifen) durch Rekursion Referentielle Transparenz: Gleiche Werte angewendet auf gleiche Funktion, liefert immer das gleiche Ergebnis Bei puren funktionalen Programmiersprachen: Funktionen sind im engen mathematischen Sinn zu sehen, als Abbildung von Werten auf Werte insbesondere keine Zuweisung: Variablen in F.P. stehen für feste Werte, nicht für veränderliche Objekte D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 2/110 3/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 4/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Funktionale Programmiersprachen (3) Einführung in Haskell I/O in Haskell Concurrent Haskell Funktionale Programmiersprachen (3) Programmieren = Schreiben von Funktionen Modulare Programmierung: Einzelne Funktionalitäten in Funktionen auslagern Ausführung der Programme: imperative Sprachen: Abarbeiten einer Befehlsfolge bei funktionalen Sprachen: Auswertung von Ausdrücken Erstellen von größeren Funktionalitäten durch Komposition von Funktionen Resultat: Wert des Ausdrucks Beispiel: Beispiel: Berechne Quadratzahl Programmieren: quadrat x = x * x quadriereUndVerdopple x = verdopple (quadrat x) Ausführen: Werte quadrat 5 aus äquivalent dazu: quadriereUndVerdopple = verdopple . quadrat quadrat 5 → 5 ∗ 5 → 25 Resultat: 25 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 5/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Haskell – Ein wenig Syntax Einführung in Haskell I/O in Haskell Concurrent Haskell Haskell – Ein wenig Syntax (2) Funktionsargumente werden “curried” geschrieben: Statt Anonyme-Funktionen: Lambda-Notation f(x,y) = x*y Beispiele: \x -> x entspricht der Identitätsfunktion (id x = x) schreibt man in Haskell \x y -> x entspricht der const-Funktion (const x y = x) f x y = x * y \x -> x*x entspricht der quadrat-Funktion Pattern-matching: Definition von Funktionen fallweise, Abarbeitung von oben nach unten, z.B. fakultaet 0 fakultaet x 6/110 Vorteil: = 1 = x*(fakultaet (x-1)) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Man kann Funktionen “in-place” definieren und verwenden Bsp.: quadriereUndVerdopple = (\x -> x+x) . (\y -> y*y) 7/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 8/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Haskell – Ein wenig Syntax (3) Einführung in Haskell I/O in Haskell Concurrent Haskell Auswertung in Haskell Einige primitive und vordefinierte Datentypen Haskell wertet verzögert (nicht-strikt, lazy, call-by-need) aus: Funktionsanwendung: Argumente werden vor der Einsetzung nicht ausgewertet Zahlen: Int, Integer (beliebig groß), Float, Double, etc. Boolesche Werte: True, False Tupel: (True,5,False) usw. Prinzip: Werte erst aus, wenn Wert benötigt wird Einige Konstrukte: if Ausdruck then Ausdruck else Ausdruck Beispiel: f1 x1,1 . . . x1,m = Expr1 ... fn xn,1 . . . xn,m = Exprn in Expr Nachgestelltes let (ungefähr): where f1 x1,1 . . . x1,m = Expr let verdopple x = x + x quadrat x = x * x nicht-strikte Auswertung (Haskell) verdopple (quadrat 5) → (quadrat 5) + (quadrat 5) → (5 ∗ 5) + (quadrat 5) → 25 + (quadrat 5) → 25 + (5 ∗ 5) → 25 + 25 → 50 Operationen: +, -, *, /, ==, <=, >=, <,>, /= D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 9/110 strikte Auswertung verdopple (quadrat 5) → verdopple (5 ∗ 5) → verdopple 25 → 25 + 25 → 50 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Auswertung in Haskell (2) 10/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Auswertung in Haskell (3) Vorteil der call-by-need Auswertung: Aufrufe terminieren häufiger In Realität: Keine Einsetzung, sondern sharing: verdopple (quadrat 5) → v → + % y $ verdopple let x = (let y = 5 in y ∗ y) in x + x let x = (quadrat 5) in x + x → + ! } * { quadrat y 5 quadrat 5 ! 5 let x = 25 in x + x → + ! 50 50 } const x y loop x =x = x + loop x Call-by-need Auswertung: const 5 (loop 3) → 5 25 } Strikte Auswertung: const 5 (loop 3) → const 5 (3 + loop 3) → const 5(3 + (3 + loop 3)) → . . . Umgang mit unendlichen Datenstrukturen funktioniert (später) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 11/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 12/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Higher-Order Functions Einführung in Haskell I/O in Haskell Concurrent Haskell Typsystem von Haskell Haskell ist stark, statisch, polymorph getypt Funktionen sind ganz normale Werte stark: jeder Ausdruck hat einen Typ Funktionen können Parameter anderer Funktion sein statisch: Zur Compilezeit kann Typcheck durchgeführt werden, keine Typfehler zur Laufzeit Funktionen können Ergebnisse von Funktionsanwendungen sein polymorph: Typen können Typvariablen enthalten (Funktionen können einen schematischen Typ haben) Beispiele Typen können vom Compiler hergeleitet werden, müssen also nicht angegeben werden Umdrehen der Argumente einer Funktion: Beispiele: flip f a b = f b a Funktionsionskomposition: quadrat :: Int -> Int -> Int const :: a -> b -> a (.) :: (b -> c) -> (a -> b) -> a -> c flip :: (a -> b -> c) -> b -> a -> c (.) f g x = f (g x) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 13/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Rekursive Datenstrukturen: Listen 14/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Listen: Beispiel Listen werden konstruiert mit map f [] = [] map f (x:xs) = (f x):map f xs [] = “Nil” = leere Liste : = ‘Cons’, Listenkonstruktor Typ von Cons: (:) :: a -> [a] -> [a] filter p [] = [] filter p (x:xs) = if p x then x:(filter p xs) else (filter p xs) Syntaktischer Zucker: [1,2,3] = 1:(2:(3:[])) Pattern-matching wird benutzt um Listen zu zerlegen. Beispiele: Beispielverwendungen tail [] = [] tail (x:xs) = xs map (*4) [1,2,3,4] ergibt [4,8,12,16] head [] = error "empty list" head (x:xs) = x D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell filter even [1,2,3,4,5] ergibt [2,4] 15/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 16/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Unendliche Listen Einführung in Haskell I/O in Haskell Concurrent Haskell Noch ein paar Listenfunktionen [] ++ ys = ys (x:xs) ++ ys = x:(xs ++ ys) repeat x = x:(repeat x) reverse [] = [] reverse (x:xs) = (reverse xs) ++ x repeat 1 ergibt [1,1,1,1,1,1,..... Aber (wegen nicht-strikter Auswertung) head (repeat 1) wertet aus zu concat [] = [] concat (xs:xxs) = xs ++ concat xxs 1 take 0 _ = [] take i [] = [] take i (x:xs) = x:(take (i-1) xs) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 17/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell List comprehensions 18/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Selbst definierte Datentypen Aufzählungstypen Mengenausdrücke, die Listen beschreiben (generieren) {x2 | x ∈ N, x > 5, gerade x} als List comprehension data Bool = True | False data RGB = Rot | Gruen | Blau [x^2 | x <- [1..], x > 4, even x] Pattern-Matching zum Selektieren Test: take 10 [x^2 | x <- [1..], x > 4, even x] [36,64,100,144,196,256,324,400,484,576] zeigeAn zeigeAn zeigeAn zeigeAn {(x, y) | x ∈ {1, 2, 3, 4, 5}, y ∈ {A, B, C}} als List comprehension :: RGB -> String Rot = "Farbe Rot" Gruen = "Farbe Grün" Blau = "Farbe Blau" [(x,y) | x <- [1,2,3,4,5], y <- [’A’,’B’,’C’]] ergibt [(1,’A’),(1,’B’),(1,’C’),(2,’A’),(2,’B’),(2,’C’),(3,’A’), (3,’B’),(3,’C’),(4,’A’),(4,’B’),(4,’C’),(5,’A’),(5,’B’),(5,’C’)] D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 19/110 isGruen :: RGB -> Bool istGruen Gruen = True istGruen _ = False D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 20/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Selbst definierte Datentypen (2) Einführung in Haskell I/O in Haskell Concurrent Haskell Selbst definierte Datentypen (3) Rekursive Datentypen Polymorphe Datentypen data Maybe a data IntBaum = Nothing | Just a data PolyBaum a = Blatt a | Knoten a (PolyBaum a) (PolyBaum a) data Either a b = Left a | Right b lookup lookup lookup if x else = Blatt Int | Knoten Int IntBaum Intbaum data List a :: a -> [(a,b)] -> Maybe b x [] = Nothing x ((y,z):ys) = == y then Just z lookup x ys D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell = Nil | Cons a (List a) Beispiel: mapBaum mapBaum mapBaum Knoten 21/110 :: (a -> b) -> PolyBaum a -> PolyBaum b f (Blatt x) = Blatt (f x) f (Knoten x bauml baumr) = (f x) (mapBaum f bauml) (mapBaum f baumr) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Selbst definierte Datentypen (4) 22/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Typklassen Zur Überladung von Operatoren, z.B. (+), (==) für verschiedene Typen Typsynonyme Beispiele type Koordinate = (Int,Int) type IntBaum = PolyBaum Int type MeineListe a = [a] (+) :: (Num a) => a -> a -> a lookup :: (Eq a) => a -> [(a, b)] -> Maybe b show :: (Show a) => a -> String D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 23/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 24/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Problematik Echte Seiteneffekte sind in Haskell nicht erlaubt. Warum? Annahme: getZahl wäre eine “Funktion”, die eine Zahl von der Standardeingabe liest Ein- und Ausgabe in Haskell Referentielle Transparenz ist verletzt, da getZahl je nach Ablauf unterschiedliche Werte liefert. Gelten noch mathematische Gleichheiten wie e + e = 2 ∗ e? Nicht für e = getZahl! length[getZahl, getZahl] fragt nach gar keiner Zahl? length [] = 0 length ( :xs) = 1 + length xs Festlegung auf eine genaue Auswertungsreihenfolge nötig. (Verhindert Optimierungen + Parallelisierung) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 25/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Monadisches I/O 26/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Monadisches I/O (2) Vorstellung: Kapselung des I/O Datentyp IO a type IO a = Welt -> (a,Welt) Wert des Typs IO a ist eine I/O-Aktion, die beim Ausführen Ein-/Ausgabe durchführt und anschließend einen Wert vom Typ a liefert. Programmiere in Haskell I/O-Aktionen, Ausführung quasi außerhalb von Haskell D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 27/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 28/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Monadisches I/O (3) Einführung in Haskell I/O in Haskell Concurrent Haskell Primitve I/O-Operationen getChar :: IO Char Werte vom Typ IO a sind Werte, d.h. sie können nicht weiter ausgewertet werden Sie können allerdings ausgeführt werden (als Aktion) In der Vorstellung: erst dann wenn eine Welt reingesteckt wird D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 29/110 putChar :: Char -> IO () D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell I/O Aktionen programmieren 30/110 Einführung in Haskell I/O in Haskell Concurrent Haskell I/O Aktionen komponieren Der >>= Operator (gesprochen: “bind”) Man braucht Operationen, um I/O Operationen miteinander zu kombinieren! (>>=) :: IO a -> (a -> IO b) -> IO b Z.B. erst ein Zeichen lesen (getChar), danach dieses Zeichen ausgeben (putChar) echo :: IO () echo = getChar >>= putChar D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 31/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 32/110 Einführung in Haskell I/O in Haskell Concurrent Haskell I/O Aktionen komponieren (3) Einführung in Haskell I/O in Haskell Concurrent Haskell I/O Aktionen komponieren (4) Der >> Operator (gesprochen: “then”) Gelesenes Zeichen zweimal ausdrucken (>>) :: IO a -> IO b -> IO b echoDup :: IO () echoDup = getChar >>= (\x -> putChar x >> putChar x) Kann mit >>= definiert werden: (>>) :: IO a -> IO b -> IO b (>>) akt1 akt2 = akt1 >>= \ -> akt2 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 33/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell I/O Aktionen komponieren (5) 34/110 Einführung in Haskell I/O in Haskell Concurrent Haskell I/O Aktionen komponieren (6) Neue I/O-Aktionen bauen, die zwei Zeichen liest und als Paar zurück liefert getTwoChars :: IO (Char,Char) getTwoChars = getChar >>= \x -> getChar >>= \y -> ???return (x,y) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Syntaktischer Zucker: do-Notation: getTwoChars :: IO (Char,Char) getTwoChars = getChar >>= \x -> getChar >>= \y -> return (x,y) 35/110 getTwoChars :: IO (Char,Char) getTwoChars = do x <- getChar; y <- getChar; return (x,y) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 36/110 Einführung in Haskell I/O in Haskell Concurrent Haskell do-Notation weg transformieren Beispiel do-Notation kann in >>= übersetzt werden: do { x<-e; s } do { e; s } do { e } Einführung in Haskell I/O in Haskell Concurrent Haskell Eine Zeile einlesen = e >>= \x -> do { s } = e >> do { s } = e getLine :: IO [Char] getLine = do c <- getChar; if c == ’\n’ then return [] else do cs <- getLine return (c:cs) Ausreichend >>= und return Wir lassen in der do-Notation, die geschweiften Klammern und ; weg. (Der Parser fügt sie automatisch, bei richtiger Einrückung ein) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 37/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Monaden 38/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Monadengesetze Eine Monade besteht aus einem Typkonstruktor M und zwei Operationen: (1) return x >>= f =f x (2) m >>= return =m (3) (>>=) :: M a -> (a -> M b) -> M b return :: a -> M a wobei zusätzlich 3 Gesetze gelten müssen. m1 >>= (\x -> m2 >>= (\y -> m3)) = (m1 >>= (\x -> m2)) >>= (\y -> m3) Man kann nachweisen, dass M = IO mit >>= und return eine Monade ist. Monade erzwingt Sequentalisierung (“I/O ist richtig implementiert”) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 39/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 40/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Monadisches I/O: Anmerkungen Beispiel: readFile readFile: Liest den Dateiinhalt aus − explizit mit Handles (= erweiterte Dateizeiger) Beachte: Es gibt keinen Weg aus der Monade heraus Aus I/O-Aktionen können nur I/O-Aktionen zusammengesetzt werden Keine Funktion vom Typ IO a -> a! -- openFile :: FilePath -> IOMode -> IO Handle -- hGetChar :: Handle -> IO Char readFile readFile do handle inhalt return Das ist nur die halbe Wahrheit! Wenn obiges gilt, funktioniert I/O sequentiell Aber: Man möchte auch “lazy I/O” Modell passt dann eigentlich nicht mehr D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell 41/110 :: FilePath -> IO String path = <- openFile path ReadMode <- leseHandleAus handle inhalt D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Beispiel: readFile (2) 42/110 Einführung in Haskell I/O in Haskell Concurrent Haskell unsafeInterleaveIO Handle auslesen: Erster Versuch leseHandleAus handle = do ende <- hIsEOF handle if ende then hClose handle >> return [] else do c <- hGetChar handle cs <- leseHandleAus handle return (c:cs) unsafeInterleaveIO :: IO a -> IO a bricht strenge Sequentialisierung auf gibt sofort etwas zurück ohne die Aktion auszuführen Aktion wird “by-need” ausgeführt: erst wenn die Ausgabe vom Typ a in IO a benötigt wird. nicht vereinbar mit “Welt”-Modell! Ineffizient: Komplette Datei wird gelesen, bevor etwas zurück gegeben wird. *Main> readFile "LargeFile" >>= print . head ’1’ 7.09 secs, 263542820 bytes D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 43/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 44/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Handle auslesen: verzögert UnsafePerformIO leseHandleAus handle = do ende <- hIsEOF handle if ende then hClose handle >> return [] else do c <- hGetChar handle cs <- unsafeInterleaveIO (leseHandleAus handle) return (c:cs) Test: *Main> readFile1 "LargeFile" ’1’ (0.00 secs, 0 bytes) Einführung in Haskell I/O in Haskell Concurrent Haskell unsafePerformIO :: IO a -> a “knackt” die Monade >>= print . head D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 45/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Implementierung von unsafeInterleaveIO 46/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Veränderliche Speicherplätze data IORef a -- Abstrakter Typ unsafeInterleaveIO :: IO a -> IO a unsafeInterleaveIO a = return (unsafePerformIO a) newIORef :: a -> IO (IORef a) readIORef :: IORef a -> IO a writeIORef :: IORef a -> a -> IO () Führt Aktion direkt mit neuer Welt aus Neue Welt wird verworfen Das Ganze wird mit return wieder in die IO-Monade verpackt Imperativ (z.B. C): int x := 0 x := x+1 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 47/110 In Haskell mit IORefs do x <- newIORef 0 y <- readIORef x writeIORef x (y+1) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 48/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Concurrent Haskell Erweiterung von Haskell um Nebenläufigkeit Concurrent Haskell Integriert in das monadische IO Beachte auch hier: Welt-Modell passt nicht Besseres Modell: Operationale Semantik direkt angeben D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 49/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Concurrent Haskell (2) 50/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Erzeugen eines nebenläufigen Threads Vorgeschlagen: 1996 (Peyton Jones, Gordon, Finne) Neue Konstrukte in Bibliothek: Control.Concurrent forkIO :: IO () -> IO ThreadId zum Erzeugen nebenläufiger Threads: forkIO t wertet t in nebenläufigen Thread aus eine neue primitive Operation Im Haupthread: sofortiges Ergebnis = eindeutige ID zur Kommunikation / Synchronisation von Threads Hauptthread läuft weiter Ende des Hauptthreads beendet alle nebenläufigen. ein neuer Datentyp mit drei primitiven Operationen D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell killThread :: ThreadId -> IO () 51/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 52/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Beispiel: Zweimal Echo Einführung in Haskell I/O in Haskell Concurrent Haskell Der Datentyp MVar MVar (mutable variable) = Bounded Buffer der Größe 1 echo i = do putStr $ "Eingabe fuer Thread" ++ show i ++ ":" line <- getLine putStrLn $ "Letzte Eingabe fuer Thread" ++ show i ++ echo i Basisoperationen: ":" ++ line newEmptyMVar :: IO (MVar a) erzeugt leere MVar takeMVar :: MVar a -> IO a zweiEchos = do forkIO (echo 1) forkIO (echo 2) block -- Hauptthread blockieren − liest Wert aus MVar, danach ist die MVar leer − falls MVar vorher leer: Thread wartet − Bei mehreren Threads: FIFO-Warteschlange putMVar :: MVar a -> a -> IO () − speichert Wert in der MVar, wenn diese leer ist − Falls belegt: Thread wartet − Bei mehreren Threads: FIFO-Warteschlange block = do block D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 53/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Der Datentyp MVar (2) 54/110 Einführung in Haskell I/O in Haskell Concurrent Haskell MVars sind binäre Semaphoren type Semaphore = MVar () MVar leer gefüllt takeMVar warten lesen MVar leer gefüllt putMVar schreiben warten Leere MVar wait wait sem :: Semaphore -> IO () = takeMVar sem newSem “erzeugt Semaphore mit 0 initialisiert” (= leere MVar) wait blockiert leere MVar (also wenn k = 0) signal füllt MVar, wenn es blockierte Prozesse gibt, kann einer wait durchführen signal bei voller MVar blockiert (bei binären Semaphoren: undefiniert) Volle MVar D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell :: IO Semaphore = newEmptyMVar signal :: Semaphore -> IO () signal sem = putMVar sem () Darstellung: val newSem newSem 55/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 56/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Schützen kritischer Abschnitte Weitere Bibliotheksfunktionen für MVar’s echoS sem i = do wait sem putStr $ "Eingabe fuer Thread" ++ show i ++ ":" line <- getLine signal sem wait sem putStrLn $ "Letzte Eingabe fuer Thread" ++ show i ++ signal sem echoS sem i zweiEchosS = do sem <signal forkIO forkIO block Einführung in Haskell I/O in Haskell Concurrent Haskell ":" ++ line newSem sem (echoS sem 1) (echoS sem 2) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 57/110 newMVar:: a -> MVar a Erzeugt eine gefüllte MVar readMVar :: MVar a -> IO a Liest den Wert einer MVar (Kombination aus take und put) swapMVar :: MVar a -> a -> IO a Tauscht den Wert einer MVar aus (Race Condition möglich) tryTakeMVar :: MVar a -> IO (Maybe a) Nicht-blockierende Version von takeMVar tryPutMVar :: MVar a -> a -> IO Bool Nicht-blockierende Version von putMVar isEmptyMVar :: MVar a -> IO Bool Testet, ob MVar leer ist modifyMVar :: MVar a -> (a -> IO a) -> IO () Funktion anwenden auf den Inhalt einer MVar. Sicher bei Exceptions: Tritt ein Fehler auf, bleibt der alte Wert erhalten D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Weitere Funktionalitäten in Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Erzeuger / Verbraucher mit 1-Platz Puffer forkIO :: IO () -> IO ThreadId Erzeugt: Einen neuen “lightweight thread”: Verwaltung durch das Laufzeitsytem Erzeuger berechnet Werte und gibt sie an den Verbraucher Synchronität: Erzeuger gibt erst den nächsten Wert, wenn Verbraucher den alten erhalten hat. Puffer durch MVar: Lässt sich direkt implementieren forkOS :: IO () -> IO ThreadId Erzeugt einen “bound thread”: Verwaltung durch das Betriebssystem type Puffer a = MVar a killThread :: ThreadId -> IO () beendet den Thread. Wenn Thread schon beendet, kein Effekt. neuer Puffer: newPuffer = newEmptyMVar yield :: IO () Forciert einen Context-Switch: Der aktuelle Thread wird von aktiv auf bereit gesetzt. Ein anderer Thread darf Schritte machen. schreiben (Erzeuger): writeToPuffer = putMVar lesen (Verbraucher): readFromPuffer = takeMVar threadDelay :: Int -> IO () Verzögert den aufrufenden Thread um die gegebene Zahl an Mikrosekunden. D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 58/110 59/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 60/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Erzeuger / Verbraucher mit 1-Platz Puffer (2) Einführung in Haskell I/O in Haskell Concurrent Haskell Speisende Philosophen newPuffer = newEmptyMVar writeToPuffer = putMVar readFromPuffer = takeMVar buff <- newBuffer ... Prozess 1 Prozess 2 do do readFromBuffer buf writeToBuffer buf 1 writeToBuffer buf 2 writeToBuffer buf 3 Implementierung: 2 1 Eine Gabel durch eine MVar () Ein Philosoph durch einen Thread D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 61/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Speisende Philosophen (2) Einführung in Haskell I/O in Haskell Concurrent Haskell Speisende Philosophen (3) 1. Implementierung: i-ter Philopsoph: 1. Implementierung: Erzeugen der Philosophen philosoph i gabeln = do let n = length gabeln -- Anzahl Gabeln takeMVar $ gabeln!!i -- nehme linke Gabel putStrLn $ "Philosoph " ++ show i ++ " hat linke Gabel ..." takeMVar $ gabeln!!(mod (i+1) n) -- nehme rechte Gabel putStrLn $ "Philosoph " ++ show i ++ " isst ..." putMVar (gabeln!!i) () -- lege linke Gabel ab putMVar (gabeln!!(mod (i+1) n)) () -- lege rechte Gabel ab putStrLn $ "Philosoph " ++ show i ++ " denkt ..." philosoph i gabeln D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 62/110 63/110 philosophen n = do -- erzeuge Gabeln (n MVars): gabeln <- sequence $ replicate n (newMVar ()) -- erzeuge Philosophen: sequence_ [forkIO (philosoph i gabeln) | i <- [0..n-1]] block Beachte: Deadlock möglich! D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 64/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Speisende Philosophen (4) Einführung in Haskell I/O in Haskell Concurrent Haskell Generelle Semaphoren Man kann generelle Semaphoren mit binären Semaphoren implementieren 2. Implementierung: N.-Philosoph in anderer Reihenfolge philosophAsym i gabeln = do let n = length gabeln -- Anzahl Gabeln if length gabeln == i+1 then -- letzter Philosoph do takeMVar $ gabeln!!(mod (i+1) n) -- nehme rechte Gabel putStrLn $ "Philosoph " ++ show i ++ " hat rechte Gabel ..." takeMVar $ gabeln!!i -- nehme linke Gabel putStrLn $ "Philosoph " ++ show i ++ " hat linke Gabel und isst" else do takeMVar $ gabeln!!i -- nehme linke Gabel putStrLn $ "Philosoph " ++ show i ++ " hat linke Gabel ..." takeMVar $ gabeln!!(mod (i+1) n) -- nehme rechte Gabel putStrLn $ "Philosoph " ++ show i ++ " hat rechte Gabel und isst" putMVar (gabeln!!i) () -- lege linke Gabel ab putMVar (gabeln!!(mod (i+1) n)) () -- lege rechte Gabel ab philosophAsym i gabeln Aber: Kodierung ziemlich kompliziert Mit Haskells MVars jedoch leicht Implementierung in Bibliothek Control.Concurrent.QSem type QSem = MVar (Int,[MVar ()]) Das Paar (Int,[MVar ()]) stellt gerade die Komponenten der Semaphore dar: Zahl k und Menge der wartenden Prozesse. Warten durch blockieren an MVars Die äußere MVar hält dieses Paar. Dadurch ist exklusiver Zugriff auf die Komponenten gesichert. D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 65/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Generelle Semaphoren (2) 66/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Generelle Semaphoren (3) wait-Operation Erzeugen einer QSem waitQSem :: QSem -> IO () waitQSem sem = do (k,blocked) <- takeMVar sem if k > 0 then putMVar sem (k-1,[]) else do block <- newEmptyMVar putMVar sem (0, blocked++[block]) takeMVar block newQSem :: Int -> IO QSem newQSem k = do sem <- newMVar (k, []) return sem D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 67/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 68/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Generelle Semaphoren (4) Einführung in Haskell I/O in Haskell Concurrent Haskell Speisende Philosophen 3. Implementierung 3. Implementierung: Mit Semaphore Raum philosophRaum i raum gabeln = do let n = length gabeln -- Anzahl Gabeln waitQSem raum -- generelle Semaphore putStrLn $ "Philosoph " ++ show i ++ " im Raum" takeMVar $ gabeln!!i -- nehme linke Gabel putStrLn $ "Philosoph " ++ show i ++ " hat linke Gabel ..." takeMVar $ gabeln!!(mod (i+1) n) -- nehme rechte Gabel putStrLn $ "Philosoph " ++ show i ++ " hat rechte Gabel und isst" putMVar (gabeln!!i) () -- lege linke Gabel ab putMVar (gabeln!!(mod (i+1) n)) () -- lege rechte Gabel ab signalQSem raum putStrLn $ "Philosoph " ++ show i ++ " aus Raum raus" putStrLn $ "Philosoph " ++ show i ++ " denkt ..." philosophRaum i raum gabeln signal-Operation signalQSem :: QSem -> IO () signalQSem sem = do (k,blocked) <- takeMVar sem case blocked of [] -> putMVar sem (k+1,[]) (block:blocked’) -> do putMVar sem (0,blocked’) putMVar block () D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell philosophenRaum n = do gabeln <- sequence $ replicate n (newMVar ()) raum <- newQSem (n-1) sequence [forkIO (philosophRaum i raum gabeln) | i <- [0..n-1]] block 69/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Erweiterte generelle Semaphoren 70/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Erweiterte generelle Semaphoren (2) wait und signal erhalten zusätzlich eine Zahl. Bei wait bedeutet die Zahl: Soviel “Ressourcen” müssen vorhanden sein, um entblockiert zu werden Erzeugen: Bei signal: Soviel Ressourcen werden zu den vorhandenen hinzu gegeben. newQSemN :: Int -> IO QSemN newQSemN initial = do sem <- newMVar (initial, []) return sem type QSemN = MVar (Int,[(Int,MVar ())]) In der Liste werden nun Paare gespeichert: Zahl, wieviel Ressourcen noch benötigt werden MVar zum blockieren. D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 71/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 72/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Erweiterte generelle Semaphoren (3) Einführung in Haskell I/O in Haskell Concurrent Haskell Erweiterte generelle Semaphoren (4) Signalisieren: signalQSemN :: QSemN -> Int -> IO () signalQSemN sem n = do (k,blocked) <- takeMVar sem (k’,blocked’) <- free (k+n) blocked putMVar sem (k’,blocked’) where free k [] = return (k,[]) free k ((req,block):blocked) | k >= req = do putMVar block () free (k-req) blocked | otherwise = do (k’,blocked’) <- free k blocked return (k’,(req,block):blocked’) Warten: waitQSemN :: QSemN -> Int -> IO () waitQSemN sem sz = do (k,blocked) <- takeMVar sem if (k - sz) >= 0 then putMVar sem (k-sz,blocked) else do block <- newEmptyMVar putMVar sem (k, blocked++[(sz,block)]) takeMVar block D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 73/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Barrierimplementierung mit QSemN D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 74/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Barrierimplementierung mit QSemN (2) type Barrier = (Int, MVar (Int, QSemN)) Erste Zahl: Anzahl der zu synchronisierenden Prozesse Zweite Zahl: Anzahl der aktuell angekommenen Prozesse QSemN: Wird benutzt um Prozesse zu blockieren MVar: Schützt Zugriff auf aktuelle Anzahl und QSemN D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 75/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 76/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Barrierimplementierung mit QSemN (3) Einführung in Haskell I/O in Haskell Concurrent Haskell Barrierimplementierung mit QSemN (4) Die Hauptfunktion ist synchBarrier newBarrier erzeugt einen neuen Barrier synchBarrier :: Barrier -> IO () synchBarrier (maxP,barrier) = do (angekommen,qsem) <- takeMVar barrier if angekommen+1 < maxP then do putMVar barrier (angekommen+1,qsem) waitQSemN qsem 1 else do signalQSemN qsem (maxP-1) putMVar barrier (0,qsem) newBarrier n = do qsem <- newQSemN 0 mvar <- newMVar (0,qsem) return (n,mvar) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 77/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Barrierimplementierung mit QSemN (5) 78/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Unbounded Buffer FIFO-Kanal, an einem Ende schreiben, am anderen Ende lesen Gewünschte Schnittstelle Code für die einzelnen Prozesse ist von der Form type Kanal a prozess_i barrier = do ’Code für die aktuelle Phase’ synchBarrier barrier -- warte auf Synchronisierung prozess_i barrier -- starte nächste Phase neuerKanal :: IO (Kanal a) schreibe :: Kanal a -> a -> IO () lese :: Kanal a -> IO a Außerdem: Keine Fehler, wenn mehrere Threads schreiben / lesen. D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 79/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 80/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Unbounded Buffer (2) Auf Strömen operieren Veränderbarer Strom ähnlich zu Listen, aber: jeder Tail eine MVar (dadurch veränderbar) leerer Strom = leere MVar type Strom a = data SCons a = Einführung in Haskell I/O in Haskell Concurrent Haskell Strom ausdrucken type Strom a = data SCons a = printStrom strom = do test <- isEmptyMVar strom if test then putStrLn "[]"+ else do SCons el tl <- readMVar strom putStr (show el ++ ":") printStrom tl MVar (SCons a) SCons a (Strom a) Strom der 1,2 und 3 enthält: do ende <- newEmptyMVar drei <- newMVar (SCons 3 ende) zwei <- newMVar (SCons 2 drei) eins <- newMVar (SCons 1 zwei) return eins D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 81/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Ströme aneinanderhängen appendStroeme strom1 strom2 = do test <- isEmptyMVar strom2 if test then return strom1 else do kopf2 <- readMVar strom2 ende <- findeEnde strom1 putMVar ende kopf2 return strom1 MVar (SCons a) SCons a (Strom a) 82/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Kanal findeEnde strom = do test <- isEmptyMVar strom if test then return strom else do SCons hd tl <- readMVar strom findeEnde tl D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 83/110 Zwei MVars, die auf Lese- und Schreibe-Ende des Stroms zeigen (Die beiden MVars synchronisieren den Zugriff) type Kanal a = (MVar (Strom a), -- Lese-Ende MVar (Strom a)) -- Schreibe-Ende D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 84/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Leeren Kanal erzeugen Einführung in Haskell I/O in Haskell Concurrent Haskell Auf Kanal schreiben schreibe :: Kanal a -> a -> IO () schreibe (read,write) val = do new_hole <- newEmptyMVar old_hole <- takeMVar write putMVar write new_hole putMVar old_hole (SCons val new_hole) neuerKanal :: IO (Kanal a) neuerKanal = do hole <- newEmptyMVar read <- newMVar hole write <- newMVar hole return (read, write) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 85/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Vom Kanal lesen Einführung in Haskell I/O in Haskell Concurrent Haskell Beispiel: Arztpraxis lese :: Kanal a -> IO a lese (read,write) = do kopf <- takeMVar read (SCons val stream) <- takeMVar kopf putMVar read stream return val D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 86/110 Eine Gemeinschaftspraxis bestehe aus n Ärzten, die in FIFO-Reihenfolge auf Patienten warten. Ankommende Patienten reihen sich ebenfalls in FIFO-Ordnung in eine Warteschlange ein. Die Arzthelferin am Empfang verteilt je einen Patienten am Anfang der ’Patientenschlange’ auf einen Arzt am Anfang der ’Ärzteschlange’. Wenn ein Arzt mit seiner Behandlung fertig ist, reiht er sich hinten in die ’Ärzteschlange’ ein. 87/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 88/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Beispiel: Arztpraxis (2) Einführung in Haskell I/O in Haskell Concurrent Haskell Beispiel: Arztpraxis (3) Hauptthread Arzthelferin (ordne) > arztpraxis n m = > do > outputsem <- newEmptyMVar > patienten_Kanal <- neuerKanal > forkIO (mapM_(schreibe patienten_Kanal) [1..m]) > aerzte_Kanal <- neuerKanal > forkIO (mapM_(schreibe aerzte_Kanal) [1..n]) > ordne patienten_Kanal aerzte_Kanal outputsem D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell > ordne patienten aerzte outputsem = > do > patient <- lese patienten > arzt <- lese aerzte > forkIO (behandle arzt patient aerzte outputsem) > ordne patienten aerzte outputsem 89/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Beispiel: Arztpraxis (2) 90/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Nichtdeterministisches Mischen Behandlung > behandle arzt patient aerzte outputsem = > do > j <- randomIO > let i = (abs j ‘mod‘ 10000000) ‘div‘ 1000000 > atomar outputsem ( > putStrLn > ("Arzt " ++ show arzt ++ " behandelt Patient " > ++ show patient ++ " in " ++ show i ++ " Sekunden") > ) > threadDelay (i*1000000) > schreibe aerzte arzt D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 91/110 Gegeben: Zwei Listen xs und ys Mische beide Listen zu einer Liste zusammen, so dass die relative Sortierung von xs und ys erhalten bleibt. Eine Liste ist partiell (ein Tail ist ⊥) =⇒ Elemente der anderen Liste erscheinen Sowohl ndMerge (1:2:bot) [3..] als auch ndMerge [3..] (1:2:bot) liefern eine unendlich lange Liste Sequentiell: Nicht möglich! (Auswertung bleibt stecken beim bot) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 92/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Nichtdeterministisches Mischen (2) Nichtdeterministisches Mischen (3) Mischen mit Kanal: schreibeListeAufKanal chan [] = schreibe chan (True,undefined) schreibeListeAufKanal chan (x:xs) = do schreibe chan (False,x) schreibeListeAufKanal chan xs Schreibe beide nebenläufig auf den Kanal Hauptthread liest den Kanal aus in eine Liste ndMerge xs ys = do chan <- neuerKanal id_s <- forkIO (schreibeListeAufKanal chan xs) id_t <- forkIO (schreibeListeAufKanal chan ys) leseKanal chan False D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell leseKanal chan flag = do (flag1,el) <- lese chan if flag1 && flag then return [] else do rest <- unsafeInterleaveIO (leseKanal chan (flag || flag1)) if flag1 then return rest else return (el:rest) 93/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Paralleles Oder 94/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Paralleles Oder (2) Sequentielles Oder: por :: Bool -> Bool -> IO Bool por s t = do ergebnisMVar <- newEmptyMVar id_s <- forkIO (if s then (putMVar ergebnisMVar True) else (putMVar ergebnisMVar t) id_t <- forkIO (if t then (putMVar ergebnisMVar True) else (putMVar ergebnisMVar s) ergebnis <- takeMVar ergebnisMVar killThread id_s killThread id_t return ergebnis a || b = if a then True else b a False True b s s ⊥ s a || b s True ⊥ s ∈ {⊥, False, True} Gewünscht: por True s = True = por s True Sequentiell: nicht möglich! D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 95/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 96/110 Einführung in Haskell I/O in Haskell Concurrent Haskell McCarthys amb Einführung in Haskell I/O in Haskell Concurrent Haskell McCarthys amb (2) amb :: a -> a -> IO a amb s t = do ergebnisMVar <- newEmptyMVar id_s <- forkIO (let x = s in seq x (putMVar ergebnisMVar x)) id_t <- forkIO (let x = t in seq x (putMVar ergebnisMVar x)) ergebnis <- takeMVar ergebnisMVar killThread id_s killThread id_t return ergebnis ambigious choice“ ” binärer Operator, der beide Argumente parallel auswertet Falls eine der beiden Auswertungen ein Ergebnis liefert, wird dies als Gesamtresultat übernommen. D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 97/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell McCarthys amb (3) 98/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Kodierung anderer Operatoren mit amb Paralleles Oder: Semantisch por2 :: Bool -> Bool -> IO Bool por2 s t = amb (if s then True else t) (if t then True else s) t, wenn s nicht terminiert s, wenn t nicht terminiert amb s t = s oder t, wenn s und t terminieren Nichtdeterministische Auswahl: choice :: a -> a -> IO a choice s t = do res <- (amb (\x -> s) (\x -> t)) return (res ()) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 99/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 100/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Erweiterung auf n Argumente Einführung in Haskell I/O in Haskell Concurrent Haskell Beispiel: Parallele Suche Gegeben: Labyrinth Gesucht: Weg vom Eingang (oben) zum Ausgang (unten) Statt zwei Argumente, eine Liste von Argumenten ambList [x] = return x ambList (x:xs) = do l <- unsafeInterleaveIO (ambList xs) amb x l D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 101/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Beispiel: Parallele Suche (2) 102/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Beispiel: Parallele Suche (3) suche = do result <- ndBFsearch [] labyrinth print result data Suchbaum a = Ziel a | Knoten a [Suchbaum a] ndBFsearch res (Ziel a) return (reverse $ a:res) labyrinth = let kn51 = Knoten (5,1) [kn52] kn52 = Knoten (5,2) [kn42,kn53,kn62] ... kn78 = Ziel (7,8) in kn51 = ndBFsearch res (Knoten a []) = do yield ndBFsearch res (Knoten a []) ndBFsearch res (Knoten a nf) = do nf’ <- mapM (unsafeInterleaveIO . ndBFsearch (a:res)) nf ambList nf’ D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 103/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 104/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Futures Einführung in Haskell I/O in Haskell Concurrent Haskell Explizite / Implizizte Futures Explizite Futures Wert eine Future muss explizit angefordert werden Futures = Variablen deren Wert am Anfang unbekannt ist, aber in der Zukunft verfügbar wird, sobald zugehörige Berechnung beendet ist. Wenn Thread Wert benötigt, führt er eine force-Operation aus: Warte bis Wert berechnet ist. In Haskell: Jede Variable “eine Future”, da verzögert ausgewertet wird. Implizite Futures keine force-Operation Nebenläufige Futures = Wert der Future wird durch nebenläufige Berechnung ermittelt. Auswertung fordert Wert automatisch an, wenn er benötigt wird. Programmierung mit impliziten Futures komfortabler! D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 105/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Explizite Futures mit MVars Einführung in Haskell I/O in Haskell Concurrent Haskell Beispiel: Parallele Baumsumme data BTree a = Leaf a | Node a (BTree a) (BTree a) type EFuture a = MVar a efuture :: IO a -> IO (EFuture a) efuture act = do ack <- newEmptyMVar forkIO (act >>= putMVar ack) return ack treeSum (Leaf a) = return a treeSum (Node a l r) = do futl <- efuture (treeSum l) futr <- efuture (treeSum r) resl <- force futl resr <- force futr let result = (a + resl + resr) in seq result (return result) force :: EFuture a -> IO a force = readMVar D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 106/110 107/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 108/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Implizite Futures: unsafeInterleaveIO Baumsumme mit impliziten Futures future :: IO a -> IO a future code = do ack <-newEmptyMVar thread <- forkIO (code >>= putMVar ack) unsafeInterleaveIO (do result <- takeMVar ack killThread thread return result) D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell treeSum (Leaf a) = return a treeSum (Node a l r) = do futl <- future (treeSum l) futr <- future (treeSum r) let result = (a + futl + futr) in seq result (return result) 109/110 D. Sabel · TIDS · WS 2013/14 · Nebenläufige Programmierung in Haskell 110/110