Nebenläufige Programmierung

Werbung
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
Herunterladen