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) WS 2011/12 Stand der Folien: 17. Januar 2012 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 1 Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Funktionale Programmiersprachen 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 2/110 3/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 4/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Funktionale Programmiersprachen (3) 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 5/110 Einführung in Haskell I/O in Haskell Concurrent Haskell TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell Haskell – Ein wenig Syntax 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)) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Man kann Funktionen “in-place” definieren und verwenden Bsp.: quadriereUndVerdopple = (\x -> x+x) . (\y -> y*y) 7/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 8/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Haskell – Ein wenig Syntax (3) Auswertung in Haskell Einige primitive und vordefinierte Datentypen Haskell wertet verzögert (nicht-strikt, lazy, call-by-need) aus: Zahlen: Int, Integer (beliebig groß), Float, Double, etc. Funktionsanwendung: Argumente werden vor der Einsetzung nicht ausgewertet 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: +, -, *, /, ==, <=, >=, <,>, /= TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 9/110 Einführung in Haskell I/O in Haskell Concurrent Haskell strikte Auswertung verdopple (quadrat 5) → verdopple (5 ∗ 5) → verdopple 25 → 25 + 25 → 50 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 10/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Auswertung in Haskell (2) Auswertung in Haskell (3) Vorteil der call-by-need Auswertung: Aufrufe terminieren häufiger In Realität: Keine Einsetzung, sondern sharing: verdopple (quadrat 5) n I nnn IIII nnn II nnn II n n II vn $ verdopple : xx :: :: xx xx :: x x : {xx quadrat let x = (let y = 5 in y ∗ y) in x + x let x = (quadrat 5) in x + x → → + % y s AAA sss AA AA sss s s A s 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) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 11/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 12/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Higher-Order Functions 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) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 13/110 Einführung in Haskell I/O in Haskell Concurrent Haskell TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 14/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Rekursive Datenstrukturen: Listen 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel filter even [1,2,3,4,5] ergibt [2,4] 15/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 16/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Unendliche Listen 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) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 17/110 Einführung in Haskell I/O in Haskell Concurrent Haskell TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 18/110 Einführung in Haskell I/O in Haskell Concurrent Haskell List comprehensions 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’)] TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 19/110 isGruen :: RGB -> Bool istGruen Gruen = True istGruen _ = False TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 20/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell Selbst definierte Datentypen (2) 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel = Nil | Cons a (List a) Beispiel: mapBaum mapBaum mapBaum Knoten 21/110 Einführung in Haskell I/O in Haskell Concurrent Haskell :: (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) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 22/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Selbst definierte Datentypen (4) 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 23/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 24/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze 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) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 25/110 Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze Monadisches I/O TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 26/110 Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 27/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 28/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze Monadisches I/O (3) Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 29/110 Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze I/O Aktionen programmieren putChar :: Char -> IO () TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 30/110 Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 31/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 32/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze I/O Aktionen komponieren (3) Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 33/110 Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze I/O Aktionen komponieren (5) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 34/110 Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze 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) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 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) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 36/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze do-Notation weg transformieren 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) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze 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 37/110 Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze Monaden TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 38/110 Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze Monadengesetze (1) return x >>= f =f x Eine Monade besteht aus einem Typkonstruktor M und zwei Operationen: (2) m >>= return =m (>>=) :: M a -> (a -> M b) -> M b return :: a -> M a (3) 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”) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 39/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 40/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze Monadisches I/O: Anmerkungen readFile: Liest den Dateiinhalt aus − explizit mit Handles (= erweiterte Dateizeiger) 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 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze Beispiel: readFile Beachte: Es gibt keinen Weg aus der Monade heraus TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 41/110 Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze Beispiel: readFile (2) :: FilePath -> IO String path = <- openFile path ReadMode <- leseHandleAus handle inhalt TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 42/110 Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 43/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 44/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze Handle auslesen: verzögert unsafePerformIO :: IO a -> a “knackt” die Monade >>= print . head TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze 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 45/110 Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze Implementierung von unsafeInterleaveIO TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 46/110 Einleitung Monadisches IO Verzögern in der IO-Monade Speicherplätze 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 Imperativ (z.B. C): Das Ganze wird mit return wieder in die IO-Monade verpackt TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 47/110 int x := 0 x := x+1 In Haskell mit IORefs do x <- newIORef 0 y <- readIORef x writeIORef x (y+1) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 48/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 49/110 Einleitung Primitive Beispiele ND-Operatoren Futures Concurrent Haskell (2) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 50/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel killThread :: ThreadId -> IO () 51/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 52/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Beispiel: Zweimal Echo Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures 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 − liest Wert aus MVar, danach ist die MVar leer − falls MVar vorher leer: Thread wartet − Bei mehreren Threads: FIFO-Warteschlange zweiEchos = do forkIO (echo 1) forkIO (echo 2) block -- Hauptthread blockieren 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 53/110 Einleitung Primitive Beispiele ND-Operatoren Futures Der Datentyp MVar (2) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 54/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel :: IO Semaphore = newEmptyMVar signal :: Semaphore -> IO () signal sem = putMVar sem () Darstellung: val newSem newSem 55/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 56/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Schützen kritischer Abschnitte 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 ":" ++ line TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 57/110 Einleitung Primitive Beispiele ND-Operatoren Futures Weitere Funktionalitäten in Concurrent Haskell 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 58/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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. TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einleitung Primitive Beispiele ND-Operatoren Futures Weitere Bibliotheksfunktionen für MVar’s newSem sem (echoS sem 1) (echoS sem 2) Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell 59/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 60/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Erzeuger / Verbraucher mit 1-Platz Puffer (2) Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 61/110 Einleitung Primitive Beispiele ND-Operatoren Futures Speisende Philosophen (2) Einführung in Haskell I/O in Haskell Concurrent Haskell 62/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 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! TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 64/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Speisende Philosophen (4) Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell 65/110 Einleitung Primitive Beispiele ND-Operatoren Futures Generelle Semaphoren (2) 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. TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 66/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 67/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 68/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Generelle Semaphoren (4) Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures 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 () TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent 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 Einleitung Primitive Beispiele ND-Operatoren Futures Erweiterte generelle Semaphoren TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 70/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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. TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 71/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 72/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Erweiterte generelle Semaphoren (3) Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 73/110 Einleitung Primitive Beispiele ND-Operatoren Futures Barrierimplementierung mit QSemN TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 74/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 75/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 76/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Barrierimplementierung mit QSemN (3) Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures 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) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 77/110 Einleitung Primitive Beispiele ND-Operatoren Futures Barrierimplementierung mit QSemN (5) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 78/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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. TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 79/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 80/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Unbounded Buffer (2) Strom ausdrucken type Strom a = data SCons a = do ende <- newEmptyMVar drei <- newMVar (SCons 3 ende) zwei <- newMVar (SCons 2 drei) eins <- newMVar (SCons 1 zwei) return eins TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel MVar (SCons a) SCons a (Strom a) printStrom strom = do test <- isEmptyMVar strom if test then putStrLn "[]"+ else do SCons el tl <- readMVar strom putStr (show el ++ ":") printStrom tl type Strom a = MVar (SCons a) data SCons a = SCons a (Strom a) Strom der 1,2 und 3 enthält: 81/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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 Einleitung Primitive Beispiele ND-Operatoren Futures Auf Strömen operieren Veränderbarer Strom ähnlich zu Listen, aber: jeder Tail eine MVar (dadurch veränderbar) leerer Strom = leere MVar Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 82/110 Einleitung Primitive Beispiele ND-Operatoren Futures Kanal findeEnde strom = do test <- isEmptyMVar strom if test then return strom else do SCons hd tl <- readMVar strom findeEnde tl TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 84/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Leeren Kanal erzeugen Einleitung Primitive Beispiele ND-Operatoren Futures 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) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell 85/110 Einleitung Primitive Beispiele ND-Operatoren Futures Vom Kanal lesen Einführung in Haskell I/O in Haskell Concurrent Haskell 86/110 Einleitung Primitive Beispiele ND-Operatoren Futures Beispiel: Arztpraxis lese :: Kanal a -> IO a lese (read,write) = do kopf <- takeMVar read (SCons val stream) <- takeMVar kopf putMVar read stream return val TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 88/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Beispiel: Arztpraxis (2) Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell > ordne patienten aerzte outputsem = > do > patient <- lese patienten > arzt <- lese aerzte > forkIO (behandle arzt patient aerzte outputsem) > ordne patienten aerzte outputsem 89/110 Einleitung Primitive Beispiele ND-Operatoren Futures Beispiel: Arztpraxis (2) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 90/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 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) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 92/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Nichtdeterministisches Mischen (2) 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 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Nichtdeterministisches Mischen (3) Mischen mit Kanal: TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 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 Einleitung Primitive Beispiele ND-Operatoren Futures Paralleles Oder TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 94/110 Einleitung Primitive Beispiele ND-Operatoren Futures Paralleles Oder (2) Sequentielles Oder: a || b = if a then True else b a False True b s s ⊥ s por :: Bool -> Bool -> IO Bool por s t = do ergebnisMVar <- newEmptyMVar id_s <- forkIO (if s then (putMVar ergebnisMVar True) else t) id_t <- forkIO (if t then (putMVar ergebnisMVar True) else s) ergebnis <- takeMVar ergebnisMVar killThread id_s killThread id_t return ergebnis a || b s True ⊥ s ∈ {⊥, False, True} Gewünscht: por True s = True = por s True Sequentiell: nicht möglich! TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 95/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 96/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures McCarthys amb Einleitung Primitive Beispiele ND-Operatoren Futures 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. TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell Einführung in Haskell I/O in Haskell Concurrent Haskell 97/110 Einleitung Primitive Beispiele ND-Operatoren Futures McCarthys amb (3) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 98/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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 ()) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 99/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 100/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Erweiterung auf n Argumente Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 101/110 Einleitung Primitive Beispiele ND-Operatoren Futures Beispiel: Parallele Suche (2) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 102/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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’ TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 103/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 104/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Futures Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures 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! TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel Einführung in Haskell I/O in Haskell Concurrent Haskell 105/110 Einleitung Primitive Beispiele ND-Operatoren Futures Explizite Futures mit MVars Einführung in Haskell I/O in Haskell Concurrent Haskell 106/110 Einleitung Primitive Beispiele ND-Operatoren Futures 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 107/110 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 108/110 Einführung in Haskell I/O in Haskell Concurrent Haskell Einleitung Primitive Beispiele ND-Operatoren Futures Implizite Futures: unsafeInterleaveIO Einleitung Primitive Beispiele ND-Operatoren Futures 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) TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 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 TIDS – Nebenläufige Programmierung in Haskell – WS 2011/12 – D. Sabel 110/110