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