Funktionale Systemprogrammierung - informatik.uni

Werbung
Funktionale
Systemprogrammierung
Haskell für Erwachsene
Christoph Lüth
3. Mai 2002
Funktionale Systemprogrammierung
1
Inhalt
•
•
•
•
•
Monadische Ein/Ausgabe
Nebenläufigkeit
Ausnahmen und Fehlerbehandlung
Sprachüberschreitende Funktionsaufrufe
Dynamische und existentielle Typen
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Systemprogrammierung
2
Grundlage
Simon Peyton Jones. Tackling the awkward squad: monadic
input/output, concurrency, exceptions and foreign-language
calls in haskell.
In Tony Hoare, Manfred Broy, and Ralf Steinbrüggen,
editors, Engineering theories of software construction, pages
47– 96. IOS Press, 2001.
http://www.research.microsoft.com/
Users/simonpj/papers/marktoberdorf/
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
4
Inhalt
•
•
•
•
•
•
Ein/Ausgabe in funktionalen Sprachen
Wo ist das Problem?
Der Datentyp IO.
Vordefinierte Funktionen für E/A.
Beispiel: Nim
Wie würde man E/A implementieren?
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
Ein- und Ausgabe in funktionalen Sprachen
• Problem:
Funktion“ readLine :: () -> String würde
”
referentielle Transparenz zerstören.
• Lösung:
Abhängigkeit von der Umwelt am Typ IO erkennbar.
IO als abstrakter Datentyp:
◦ IO t
Funktion vom Typ t, die Ein/Ausgabe betreibt (Aktion)
◦ Aktionen können nur mit Aktionen komponiert werden
einmal IO, immer IO“
”
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
5
Funktionale Ein/Ausgabe
• IO als abstrakter Datentyp:
type IO t
(>>=) :: IO a-> (a-> IO b)-> IO b
return :: a-> IO a
• Vordefinierte Funktionen (Prelude):
◦ Zeile von stdin lesen:
getLine :: IO String
◦ String ausgeben:
putStr
:: String-> IO ()
◦ String mit Zeilenvorschub ausgeben:
putStrLn :: String-> IO ()
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
6
Funktionale Ein/Ausgabe
• Ein einfaches Beispiel:
echo :: IO ()
echo = getLine >>= putStrLn >>= \_ -> echo
• Immer noch einfach:
ohce :: IO ()
ohce = getLine >>= putStrLn . reverse >> ohce
• Vordefinierte Abkürzung:
(>>) :: IO t-> IO u-> IO u
f >> g = f >>= \_ -> g
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
7
Funktionale Ein/Ausgabe
Syntaktischer Zucker für IO
• Abkürzende Schreibweise:
echo =
echo =
do s<- getLine
getLine
putStrLn s
>>= \s-> putStrLn s ⇐⇒
>> echo
echo
◦ Rechts sind >>=, >> implizit.
• Es gilt die Abseitsregel.
◦ Einrückung der ersten Anweisung nach do bestimmt Abseits.
◦ Ansonsten sagt hugs:
Last generator in do {...} must be an expression
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
8
Funktionale Ein/Ausgabe
9
Ein/Ausgabe in Haskell98
•
•
•
•
Ein/Ausgabe aus Dateien und stdin,out (Modul IO)
Fehlerbehandlung
Zufallszahlen (Modul Random)
Zugriff auf das Dateisystem (Modul Directory)
◦ Leider nicht in hugs.
• Kommandozeile, Umgebungsvariablen (Modul System)
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
10
Ein/Ausgabe mit Dateien
• Im Prelude vordefiniert:
◦ Dateien schreiben (überschreiben, anhängen):
type FilePath = String
writeFile
:: FilePath -> String -> IO ()
appendFile
:: FilePath -> String -> IO ()
◦ Datei lesen (verzögert):
readFile
:: FilePath
-> IO String
• Mehr Operationen im Modul IO der Standardbücherei
◦ Buffered/Unbuffered, Seeking, &c.
◦ Operationen auf Handle
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
• Ein Beispiel:
wc :: String-> IO ()
wc file =
do c<- readFile file
putStrLn (show (length (lines
++ "
putStrLn (show (length (words
++ "
putStrLn (show (length c)++ "
11
c))
lines ")
c))
words, and ")
characters. ")
◦ Nicht sehr effizient — Inhalt der Datei wird im Speicher gehalten.
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
Noch ein Beispiel: Nim revisited
• Implementation von Nim:
◦ Am Anfang Anzahl der Hölzchen auswürfeln.
◦ Eingabe des Spielers einlesen.
◦ Wenn nicht mehr zu gewinnen, aufgeben, ansonsten ziehen.
◦ Wenn ein Hölzchen über ist, hat Spieler verloren.
• Zufallszahlen: Modul Random
◦ class Random a where
randomRIO :: (a, a)-> IO a
randomIO :: IO a
◦ Instanzen von Random: Basisdatentypen.
◦ Random enthält ferner Zufallsgeneratoren für Pseudozufallszahlen.
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
12
Funktionale Ein/Ausgabe
• Nim revisited:
◦ Importe und Hilfsfunktionen:
import Random (randomRIO)
import IO(hFlush,stdout)
◦ wins
wins
wins
if
liefert Just n, wenn Zug n gewinnt; ansonsten Nothing
:: Int-> Maybe Int
n =
m == 0 then Nothing else Just m where
m = (n- 1) ‘mod‘ 4
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
13
Funktionale Ein/Ausgabe
14
◦ Hauptfunktion:
play :: Int-> IO ()
play n =
do putStrLn ("Es sind "++ show n ++
" Hölzchen im Haufen.")
if n== 1 then putStrLn "Ich habe gewonnen!"
else do m<- getInput
case wins (n-m) of
Nothing -> putStrLn "Ich gebe auf."
Just l -> do putStrLn ("Ich nehme "
++ show l)
play (n-(m+l))
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
◦ Noch zu implementieren: Benutzereingabe
getInput’ :: IO Int
getInput’ =
do putStr "Wieviele nehmen Sie? "
n <- do s<- getLine
return (read s)
if n<= (0::Int) || n>3 then
do putStrLn "Ungültige Eingabe!"
getInput’
else return n
◦ Nicht sehr befriedigend: Abbruch bei falscher Eingabe.
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
15
Funktionale Ein/Ausgabe
• Fehlerbehandlung:
◦ Fehler werden durch abstrakten Datentyp IOError repräsentiert
◦ Fehlerbehandlung durch Ausnahmen (ähnlich Java)
ioError :: IOError -> IO a
-- "throw"
catch
:: IO a-> (IOError-> IO a) -> IO a
◦ Nur in IO t können Fehler behandelt werden.
◦ Fangbare Benutzerfehler mit userError::String-> IOError.
◦ IOError kann analysiert werden— Auszug aus Modul IO:
isIllegalOperation
:: IOError -> Bool
isPermissionError
:: IOError -> Bool
isUserError
:: IOError -> Bool
ioeGetErrorString
:: IOError -> String
ioeGetFileName
:: IOError -> Maybe FilePath
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
16
Funktionale Ein/Ausgabe
17
• Robuste Eingabe:
◦ readIO :: Read a=> String-> IO a wirft im Fehlerfall Ausnahme,
die gefangen werden kann.
getInput :: IO Int
getInput =
do putStr "Wieviele nehmen Sie? "
hFlush stdout
n <- catch (do s<- getLine; readIO s)
(\_ -> do putStrLn "Eingabefehler."
getInput)
if n<=(0::Int) || n>3 then
do putStrLn "Ungültige Eingabe!"; getInput
else return n
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
• Haupt- und Startfunktion:
◦ Begrüßung
◦ Anzahl Hölzchen auswürfeln
◦ starten.
main :: IO ()
main = do putStrLn "\nWillkommen bei Nim!\n"
n <- randomRIO(5,49)
play n
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
18
Funktionale Ein/Ausgabe
19
Aktionen als Werte
• Aktionen sind Werte wie alle anderen.
• Dadurch Definition von Kontrollstrukturen möglich:
◦ Endlosschleife:
forever :: IO a-> IO a
forever a = a >> forever a
◦ Iteration (feste Anzahl)
forN :: Int-> IO a-> IO ()
forN n a | n == 0
= return ()
| otherwise = a >> forN (n-1) a
◦ Besser:
forN n = sequence_ . (take n). repeat
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
◦ Iteration (variabel, wie for in Java)
for :: (a, a-> Bool, a-> a)-> (a-> IO ())-> IO ()
for (start, cont, next) cmd =
iter start where
iter s = if cont s then cmd s >> iter (next s)
else return ()
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
20
Funktionale Ein/Ausgabe
• Vordefinierte Kontrollstrukturen (Prelude)
◦ Listen bearbeiten:
sequence :: [IO a]-> IO [a]
sequence (c:cs) = do x <- c
xs <- sequence cs
return (x:xs)
sequence_ :: [IO ()]-> IO ()
◦ Map für Monaden:
mapM :: (a-> IO b)-> [a]-> IO [b]
mapM f = sequence . map f
mapM_ :: (a-> IO ())-> [a]-> IO ()
mapM_ f = sequence_ . map f
◦ Andere (wie filterM) im Modul Monad.
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
21
Funktionale Ein/Ausgabe
Der Glasgow Haskell Compiler.
• Es muss ein Hauptmodul mit einer Hauptfunktion geben:
module Main where ...
main :: IO ()
• Aufruf mit ghc [-o Executable] file.hs
◦ Viele, viele Optionen!
• Pakete einbinden mit -package, z.B.
◦ lang für IOExts und Freunde
◦ concurrent für Nebenläufigkeit
• Interaktive Version: ghci
• Integriertes Make: ghc --make.
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
22
Funktionale Ein/Ausgabe
23
Ein/Ausgabe mit dem ghc
Das Posix-Modul bietet
• Prozeßprimitive (exec, fork)
• Prozeßumgebung und Systemdatenbasis
• Operationen auf Dateien
• Primitive I/O (Operationen auf fd)
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
24
Referenzen
Referenzen sind veränderliche Variablen:
data IORef a
newIORef
:: a-> IO (IORef a)
readIORef :: IORef a-> IO a
writeIORef :: IORef a-> a-> IO ()
Zur Benutzung: import IOExts
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
25
Beispiel: fac revisited.
fac :: Int-> IO Int
fac n = do r<- newIORef 1
for (1, (<= n), (+1)) (multRef r)
readIORef r where
multRef :: IORef Int-> Int-> IO ()
multRef r m = do n<- readIORef r
writeIORef r (n*m)
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
26
IO für Profis
• IO sorgt nur für die richtige Reihenfolge.
• Manchmal ist das unnötig:
◦ Reihenfolge definiert (Aktion immer am Anfang, z.B. Initialisierung)
◦ Reihenfolge egal (von anderen Aktionen unabhängig,
Debuggingausgaben)
• Aus dem Modul IOExts:
unsafePerformIO :: IO a-> a
• Handle with extreme care!
◦ Zum Beispiel: inlining verhindern {-# NOINLINE ... #-}
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
Der Blick hinter die Kulissen.
• IO a ist keine schwarze Magie.
• Grundprinzip:
◦ Der Systemzustand wird durch das Programm gereicht.
◦ Darf dabei nie dupliziert werden.
◦ Auswertungsreihenfolge muß erhalten bleiben.
• Implementation:
◦ Mit Systemzustand S:
type IO’ a = (S-> (a, S))
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
27
Funktionale Ein/Ausgabe
28
• Komposition:
◦ Wir wissen: a-> s-> (b, s) ∼
= (a, s)-> (b, s)
◦ Damit Definition von (>>=):
(>>=) :: IO’ a-> (a-> IO’ b)-> IO’ b
:: s-> (a, s)-> (a-> (s-> (b, s))) -> (s-> (b, s))
:: s-> (a, s)-> ((a, s)-> (b, s)) -> (s-> (b, s))
◦ Entspricht (>.>) :: (a-> b)-> (b-> c)-> (a-> c)
◦ Im Prinzip:
(>>=) :: IO’ a-> (a-> IO’ b)-> IO’ b
f >>= g = f >.> uncurry g
Aber:
Typsynonym nicht abstrakt.
(>>=) gehört zu Klasse Monad.
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
• Komposition:
◦ IO’ als abstrakter Datentyp, Systemzustand s als Parameter:
data IO’ s a = IO’ (s-> (a, s))
◦ Lokale Funktion: IO’ auspacken
unwrap :: IO’ s a-> (s-> (a, s))
unwrap (IO’ f) = f
◦ Instanz der Typklasse Monad:
instance Monad (IO’ s) where
f >>= g
= IO’ (unwrap f >.> uncurry (unwrap. g))
return a = IO’ (\s-> (a, s))
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
29
Funktionale Ein/Ausgabe
30
• Implementation von Ein/Ausgabe:
◦ Systemzustand: Eingabestrom und Ausgabestrom
type IO1 = IO’ (String, String)
◦ Eingabe: ersten String in Eingabestrom
getLine’ :: IO1 String
getLine’ = IO’ f where
f (i, o)= (hd, (if null tl then tl
else tail tl, o)) where
(hd, tl) = span (/= ’\n’) i
◦ Ausgabe: String an den Ausgabestrom hängen
putStr’ :: String-> IO1 ()
putStr’ s = IO’ (\(i, o)-> ((), (i, o++ s)))
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
31
◦ Programm laufen lassen:
run :: IO1 ()-> String-> String
run prog i = o where
IO’ p = prog
((), (_, o))= p (i, [])
◦ Beispielprogramm:
countXs :: IO1 ()
countXs = cnt 0 where
cnt x = do putStr’ ("Found "++ show x ++ " crosses.\n")
s<- getLine’
if null s then return ()
else cnt (x+ length (filter (’x’ ==)
(map toLower s)))
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Funktionale Ein/Ausgabe
32
Zusammenfassung
• Ein/Ausgabe in Haskell durch IO a:
◦ Berechungen vom IO a hängen von Umwelt ab.
◦ Komposition von IO a durch
(>>=) :: IO a-> (a-> IO b)-> IO b
return :: a-> IO a
◦ Fehlerbehandlung durch Ausnahmen (IOError, catch).
• Verschiedene Funktionen der Standardbücherei:
◦ Prelude: getLine, putStr, putStrLn
◦ Module: IO, Random
• Implementation von IO:
◦ Explizite Modellierung des Systemzustandes.
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Grundlagen der nebenläufigen
Programmierung in Haskell
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Grundlagen der nebenläufigen Programmierung in Haskell
34
Konzepte der Nebenläufigkeit
• Thread (lightweight process)
vs.
Programmiersprache
Prozess
Betriebssystem
(z.B. Java, Haskell)
gemeinsamer Speicher
getrennter Speicher
mehrere pro Programm
einer pro Programm
• Multitasking:
präemptiv: Kontextwechsel kann erzwungen werden
kooperativ: Kontextwechsel nur freiwillig
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Grundlagen der nebenläufigen Programmierung in Haskell
35
Zur Erinnerung: Threads in Java
•
•
•
•
•
Erweiterung der Klassen Thread oder Runnable
Gestartet wird Methode run() — durch eigene überladen
Starten des Threads durch Aufruf der Methode start()
Kontextwechsel mit yield()
Je nach JVM kooperativ oder präemptiv.
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Grundlagen der nebenläufigen Programmierung in Haskell
Threads in Haskell: Concurrent Haskell
• Sequentielles Haskell: Reduktion eines Ausdrucks
• Nebenläufiges Haskell: Reduktion eines Ausdrucks an
mehreren Stellen
• ghc (und Hugs) implementieren Haskell-Threads
• ghc: präemptiv, Hugs: kooperativ
• Modul Concurrent enthält Basisfunktionen
• Wenige Basisprimitive, darauf aufbauend Abstraktionen
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
36
Concurrent Haskell
37
Concurrent Haskell
• Jeder Thread hat einen Identifier — Typ ThreadId
• Neuen Thread erzeugen:
forkIO:: IO()-> IO ThreadId
• Thread stoppen:
killThread :: ThreadId -> IO ()
• Kontextwechsel:
yield :: IO ()
• Eigener Thread:
myThreadId :: IO ThreadId
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Concurrent Haskell
• Warten:
threadDelay :: Int -> IO ()
◦ Argument in Mikrosekunden
◦ Auflösung ∼
= 50ms
• Blockierung:
◦ Blockierende Systemaufrufe blockieren alle Threads (den gesamten
Haskell-Prozeß)
◦ Aber: Haskell Standard-IO blockiert nur den aufrufenden Thread
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
38
Concurrent Haskell
Concurrent Haskell — erste Schritte
Ein einfaches Beispiel:
import Concurrent
write :: Char-> IO ()
write c = putChar c >> write c
main :: IO ()
main = forkIO (write ’X’) >> write ’O’
Übersetzen: ghc -package concurrent simple.hs
Mit Hugs keine besonderen Optionen nötig.
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
39
Synchronisation mit MVars
40
Synchronisation mit MVars
• MVar a veränderbare Variable:
• Entweder leer oder gefüllt mit einem a
• Verhalten beim Lesen und Schreiben
Zustand vorher: leer
Lesen
blockiert
Schreiben
danach gefüllt
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
gefüllt
danach leer
?
Synchronisation mit MVars
• Neue Variable erzeugen (leer oder gefüllt):
newEmptyMVar :: IO (MVar a)
newMVar :: a -> IO (MVar a)
• Lesen:
takeMVar :: MVar a -> IO a
• Schreiben:
putMVar :: MVar a -> a -> IO ()
• Nicht-blockierendes Lesen:
tryTakeMVar :: MVar a -> IO (Maybe a)
• Test (Achtung: Zustand kann sich ändern)
isEmptyMVar :: MVar a -> IO Bool
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
41
Synchronisation mit MVars
42
Ein einfaches Beispiel ohne Synchronisation
• Nebenläufige Eingabe von der Tastatur
echo :: String-> IO ()
echo p =
do putStrLn ("\nPlease enter line for "++p)
line <- getLine
randomRIO (1,100) >>= \n-> forN n (putStr (p++ line))
echo p
main :: IO ()
main = forkIO (echo "1") >> echo "2"
• Problem: gleichzeitige Eingabe
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Synchronisation mit MVars
42
Ein einfaches Beispiel ohne Synchronisation
• Nebenläufige Eingabe von der Tastatur
echo :: String-> IO ()
echo p =
do putStrLn ("\nPlease enter line for "++p)
line <- getLine
randomRIO (1,100) >>= \n-> forN n (putStr (p++ line))
echo p
main :: IO ()
main = forkIO (echo "1") >> echo "2"
• Problem: gleichzeitige Eingabe
• Lösung: MVar synchronisiert Eingabe
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Synchronisation mit MVars
43
Ein einfaches Beispiel mit Synchronisation
• MVar voll ⇔ Eingabe möglich
◦ Also: initial voll
• Inhalt der MVar irrelevant: MVar ()
echo :: MVar ()-> String-> IO ()
echo flag p =
do takeMVar flag
putStrLn ("\nPlease enter line "++p)
line <- getLine
putMVar flag ()
randomRIO (1,100) >>= \n-> forN n (putStr (p++ line))
yield -- noetig, da sonst keine Fairness.
echo flag p
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Synchronisation mit MVars
Das Standardbeispiel:
Die speisenden Philosophen
• Philosoph i:
◦ vor dem Essen i-tes und (i + 1) mod n-tes Stäbchen nehmen
◦ nach dem Essen wieder zurücklegen
• Stäbchen modelliert als MVar ()
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
44
Synchronisation mit MVars
45
• i-ter Philosoph:
philo :: [MVar ()] -> Int-> IO ()
philo chopsticks i =
let num_phil = length (chopsticks)
in do putStrLn ("Phil #"++(show i)++" thinks...")
takeMVar (chopsticks !! i)
takeMVar (chopsticks !! ((i+1) ‘mod‘ num_phil))
putStrLn ("Phil #"++(show i)++" eats...")
putMVar (chopsticks !! i) ()
putMVar (chopsticks !! ((i+1) ‘mod‘ num_phil)) ()
philo chopsticks i
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Synchronisation mit MVars
46
• Hauptfunktion: n Stäbchen erzeugen
• Anzahl Philosophen in der Kommandozeile
main :: IO ()
main = do num <- getArgs >>= \a-> return (read (head a))
chopsticks <- sequence
(take num (repeat (newMVar ())))
mapM_ (forkIO . (philo chopsticks)) [0.. num-1]
block
• Hilfsfunktion block: blockiert aufrufenden Thread
block :: IO ()
block = newEmptyMVar >>= takeMVar
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Abstraktionen
47
Abstraktionen
Mit MVars mächtigere Synchronisationskonzepte
implementieren:
• Lese/Schreiber-Probleme: Buffer und buffernde Kanäle
• Semaphoren (allgemein, quantitativ)
• Monitore und Mutexe
• Buffer, Kanäle, Semaphoren schon im Modul Concurrent
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Abstraktionen I: Puffer
48
Abstraktionen I: Puffer
•
•
•
•
Leser-Schreibe-Problem: Kein Überschreiben.
Eine MVar a für Daten Schreiber→Leser
Eine MVar () für Synchronisation Leser→Schreiber
Neuen Puffer erzeugen:
data CVar a = CVar (MVar a) (MVar ())
newCVar :: IO (CVar a)
newCVar =
do datum <- newEmptyMVar
ack
<- newMVar ()
return (CVar datum ack)
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Abstraktionen I: Puffer
• Puffer schreiben:
writeCVar :: CVar a -> a -> IO ()
writeCVar (CVar datum ack) val =
do takeMVar ack
putMVar datum val
return ()
• Puffer lesen:
readCVar :: CVar a -> IO a
readCVar (CVar datum ack) =
do val<- takeMVar datum
putMVar ack ()
return val
• Nachteil: immer nur einen Wert zur Zeit.
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
49
Abstraktion II: Kanäle
50
Abstraktion II: Kanäle
• Ein Kanal hat einen Strom mit einem Lese- und
Schreibende:
data Chan a = Chan (MVar (Stream a)) (MVar (Stream a))
◦ Hier MVar, damit Lesen/Schreiben nicht unterbrochen wird
• Ein Strom ist
◦ entweder leer,
◦ oder enthält Werte (Typ ChItem a) aus Kopf a und Rest.
type Stream a = MVar (ChItem a)
data ChItem a = ChItem a (Stream a)
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Abstraktion II: Kanäle
• Schnittstelle:
newChan
:: IO (Chan a)
writeChan :: Chan a -> a -> IO ()
readChan :: Chan a -> IO a
• Neuen Kanal erzeugen:
newChan :: IO (Chan a)
newChan =
do hole <- newEmptyMVar
read <- newMVar hole
write <- newMVar hole
return (Chan read write)
◦ NB: Leseende = Schreibende
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
51
Abstraktion II: Kanäle
52
In einen Kanal schreiben
• Neues Ende (hole) anlegen
• Wert in altes Ende schreiben
• Zeiger auf neues Ende setzen
writeChan (Chan _read write) val =
do new_hole <- newEmptyMVar
old_hole <- takeMVar write
putMVar write new_hole
putMVar old_hole (ChItem val new_hole)
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Abstraktion II: Kanäle
53
Aus Kanal lesen
• Anfang auslesen, Anfangszeiger weitersetzen
• Kann blockieren (wenn Kanal leer)
readChan (Chan read _write) =
do read_end <- takeMVar read
(ChItem val new_read_end)
<- readMVar read_end
putMVar read new_read_end
return val
◦ readMVar :: MVar a-> IO a liest MVar, schreibt Wert zurück.
◦ readMVar statt takeMVar, um Duplikation zu ermöglichen
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Abstraktion II: Kanäle
54
Weitere Kanalfunktionen
• Zeichen wieder vorne einhängen:
unGetChan :: Chan a -> a -> IO ()
• Kanal duplizieren (Multicast):
dupChan :: Chan a -> IO (Chan a)
• Kanalinhalt als (unendliche) Liste:
getChanContents :: Chan a -> IO [a]
◦ Auswertung terminiert nicht, sondern blockiert
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Abstraktionen III: Semaphoren
Abstraktionen III: Semaphoren
• Abstrakter Datentyp QSem
• Betreten des kritischen Abschnitts (P):
waitQSem :: QSem -> IO ()
• Verlassen des kritischen Abschnitts (V):
signalQSem :: QSem -> IO ()
• Semaphore: Zähler plus evtl. wartende Threads
◦ P erniedrigt Zähler, blockiert ggf. aufrufenden Thread
◦ V erhöht Zähler, gibt ggf. blockierte Threads frei
• Implementierung von Semaphoren mit MVar:
eigenes Scheduling
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
55
Abstraktionen III: Semaphoren
Semaphoren: die P-Operation
data QSem = QSem (MVar (Int, [MVar ()]))
newQSem :: Int -> IO QSem
waitQSem :: QSem -> IO ()
waitQSem (QSem sem) = do
(avail,blocked) <- takeMVar sem
if avail > 0 then
putMVar sem (avail-1,[])
else do
block <- newEmptyMVar
putMVar sem (0, blocked++[block])
takeMVar block
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
56
Abstraktionen III: Semaphoren
Semaphoren: die V-Operation
signalQSem :: QSem -> IO ()
signalQSem (QSem sem) = do
(avail,blocked) <- takeMVar sem
case blocked of
[] -> putMVar sem (avail+1,[])
(block:blocked’) -> do
putMVar sem (0,blocked’)
putMVar block ()
• Alternatives Scheduling:
◦ am Anfang hinzufügen, vom Anfang nehmen
(einfacher, kann aushungern)
◦ zufällige Auswahl
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
57
Abstraktion IV: Monitore und Mutexe
Abstraktion IV: Monitore und Mutexe
• Monitore schützen Aktionen
• Modellierung als Typklasse:
class Synchronize v where
synchronize :: v -> IO a -> IO a
◦ Parameter v: Datenstruktur über der synchronisiert wird
• Beispiel: Semaphoren
instance Synchronize QSem where
synchronize s a = do waitQSem s
r<- a
signalQSem s
return r
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
58
Abstraktion IV: Monitore und Mutexe
Reentrante Mutexe
• Problem: Semaphoren (u.a.) nicht reentrant,
keine Rekursion möglich
• Lösung: reentrantes Mutex (mutual exclusion)
• Operationen: acquire und release
• Mutex enthält:
◦ ThreadId des besitzenden Threads, Zähler für Rekursionstiefe
◦ Liste von blockierten Threads (Id, MVar)
data Mutex = Mutex (MVar (Maybe (ThreadId,Int),
[(ThreadId, MVar ())]))
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
59
Abstraktion IV: Monitore und Mutexe
60
• Mutex akquirieren:
acquire :: Mutex -> IO ()
◦ Wenn Thread Mutex besitzt, dann Zähler erhöhen;
◦ sonst neue MVar () erzeugen, ThreadId und MVar in Liste einhängen,
auf MVar warten;
• Mutex freigeben:
release :: Mutex -> IO ()
◦ Prüfen, ob Thread Mutex besitzt (sonst Fehler)
◦ Wenn Zähler 1 und Liste leer, wird Mutex leer
◦ Wenn Zähler 1, dann ersten Thread aus Liste aufwecken
◦ Wenn Zähler größer 1, dann erniedrigen
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Abstraktion IV: Monitore und Mutexe
61
• Mutex akquirieren:
acquire :: Mutex -> IO ()
acquire (Mutex mvar) =
do st <- takeMVar mvar
current <- myThreadId
case st of
(Nothing,[]) -> putMVar mvar (Just (current,1),[])
(Just (holder,n),pnd) ->
if current == holder
then putMVar mvar (Just (holder,n+1),pnd)
else do bsem <- newEmptyMVar
putMVar mvar (Just (holder,n),
(current,bsem):pnd)
takeMVar bsem
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Abstraktion IV: Monitore und Mutexe
62
release :: Mutex -> IO ()
release (Mutex mvar) =
do st <- takeMVar mvar
current <- myThreadId
case st of
(Just (h,n),pnd) | current == h ->
release’ mvar h n pnd
_ -> do putMVar mvar st
error "Illegal lock release" where
release’ mvar _ 1 [] = putMVar mvar (Nothing,[])
release’ mvar _ 1 ((h’,sem):pnd’) =
do putMVar mvar (Just (h’,1),pnd’)
putMVar sem ()
release’ mvar h n pnd = putMVar mvar (Just (h,n-1), pnd)
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Abstraktion IV: Monitore und Mutexe
Reentrante Monitore
Mit Mutex Implementierung reentranter Monitore:
instance Synchronize Mutex where
synchronize m a =
do acquire m
r<- a
release m
return r
Rekursion möglich (aber teuer).
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
63
Abstraktion IV: Monitore und Mutexe
Zusammenfassung
Concurrent Haskell bietet
• Threads auf Quellsprachenebene
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
64
Abstraktion IV: Monitore und Mutexe
Zusammenfassung
Concurrent Haskell bietet
• Threads auf Quellsprachenebene
• Synchronisierung mit MVars
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
64
Abstraktion IV: Monitore und Mutexe
Zusammenfassung
Concurrent Haskell bietet
• Threads auf Quellsprachenebene
• Synchronisierung mit MVars
• Durch schlankes Design einfache Implementierung
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
64
Abstraktion IV: Monitore und Mutexe
Zusammenfassung
Concurrent Haskell bietet
• Threads auf Quellsprachenebene
• Synchronisierung mit MVars
• Durch schlankes Design einfache Implementierung
• Funktionales Paradigma erlaubt Abstraktionen:
Channels, Semaphoren, Monitore, . . .
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
64
Abstraktion IV: Monitore und Mutexe
64
Zusammenfassung
Concurrent Haskell bietet
• Threads auf Quellsprachenebene
• Synchronisierung mit MVars
• Durch schlankes Design einfache Implementierung
• Funktionales Paradigma erlaubt Abstraktionen:
Channels, Semaphoren, Monitore, . . .
• Haskell: the world’s finest imperative concurrent language.
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Längeres Beispiel: It’s good to talk
Längeres Beispiel: It’s good to talk
• Ziel: ein Programm, um sich über das Internetz zu
unterhalten (talk, IRC, etc.)
• Dazu: ein Server mit netzweit bekannter Adresse, zu dem
Verbindung aufgenommen wird, und der Nachrichten an
alle angeschlossenen Clients weiterverteilt.
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
65
Längeres Beispiel: It’s good to talk
Socketprogrammierung
• Serverseite:
◦ Socket erzeugen, mit bind an Namen binden, mit listen
Verbindungsbereitschaft anzeigen
◦ Mit accept auf eingehende Verbindungen warten und annehmen.
◦ Jede Verbindung erzeugt neuen Filedescriptor
⇒ inhärent nebenläufiges Problem!
• Clientseite: Socket erzeugen, mit connect Verbindung
aufnehmen
• GHC-Modul Socket
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
66
Längeres Beispiel: It’s good to talk
67
Module Socket where
type Socket
data PortID = Service String -- z.B. "ftp"
| PortNumber PortNumber
| UnixSocket String -- socket in file system
type Hostname = String
mkPortNumber :: Int-> PortNumber
-- Serverside:
listenOn
:: PortID-> IO Socket
accept
:: Socket-> IO (Handle, Hostname, PortNumber)
-- Clientside:
connectTo :: Hostname -> PortID -> IO Handle
socketPort :: Socket-> IO PortID
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Längeres Beispiel: It’s good to talk
68
Serverarchitektur
• Ein Kanal zur Nachrichtenverbreitung:
◦ eine Nachricht, viele Empfänger (broadcast)
◦ Realisierung mittels dupChan
• Zentraler Scheduler
• Für jede ankommende Verbindung neuer Thread:
◦ Nachrichten vom Socket auf den Kanal schreiben
◦ Nachrichten vom Kanal in den Socket schreiben
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Längeres Beispiel: It’s good to talk
• Problem: Wie aus Socket oder Kanal lesen wenn beide
blockieren?
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
69
Längeres Beispiel: It’s good to talk
• Problem: Wie aus Socket oder Kanal lesen wenn beide
blockieren?
• Nicht mit select (BSD) oder poll (SysV) blockieren
gesamten Haskell-Prozeß
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
69
Längeres Beispiel: It’s good to talk
• Problem: Wie aus Socket oder Kanal lesen wenn beide
blockieren?
• Nicht mit select (BSD) oder poll (SysV) blockieren
gesamten Haskell-Prozeß
• Lösung: Zwei Threads
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
69
Längeres Beispiel: It’s good to talk
• Problem: Wie aus Socket oder Kanal lesen wenn beide
blockieren?
• Nicht mit select (BSD) oder poll (SysV) blockieren
gesamten Haskell-Prozeß
• Lösung: Zwei Threads
• Clientarchitektur: telnet
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
69
Längeres Beispiel: It’s good to talk
Talk 0.1: Hauptprogramm
main :: IO ()
main =
do port_num <- getArgs >>= return . read . head
s <- listenOn (PortNumber (fromInteger port_num))
ch<- newChan
loop s ch
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
70
Längeres Beispiel: It’s good to talk
71
Talk 0.1: Hauptschleife
loop :: Socket -> Chan String -> IO ()
loop s ch =
do (handle, wh, p) <- accept s
hSetBuffering handle NoBuffering
putStrLn ("New connection from "++ wh++ " on "++ show p)
ch2
<- dupChan ch
forkIO (newUser handle ch)
loop s ch2
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Längeres Beispiel: It’s good to talk
Talk 0.1: Benutzerprozeß
newUser :: Handle-> Chan String -> IO ()
newUser socket msgch =
forkIO read >> write where
read :: IO ()
read = hGetLine socket >>= writeChan msgch >> read
write :: IO ()
write = readChan msgch >>= hPutStrLn socket >> write
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
72
Längeres Beispiel: It’s good to talk
73
Talk 0.1: Zusammenfassung
Nachteile:
• Nachrichten stauen sich im Kanal
• Serverprozess fällt um, wenn Benutzer Verbindung beendet
• Keine Fehlerbehandlung
• Benutzer anonym
Übersetzen mit -package concurrent -package net
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Längeres Beispiel: It’s good to talk
74
Talk 0.2: Hauptprogramm
main :: IO ()
main =
do port_num <- getArgs >>= return . read . head :: IO Intege
s <- listenOn (PortNumber (fromInteger port_num))
loop s Nothing
• Kanäle nach Bedarf erzeugen
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Längeres Beispiel: It’s good to talk
75
Talk 0.2: Hauptschleife
loop :: Socket -> Maybe (Chan String) -> IO ()
loop s ch =
do (handle, wh, p) <- accept s
hSetBuffering handle NoBuffering
putStrLn ("New connection from "++ wh++ " on port "++ sho
ch2 <- case ch of Just c -> dupChan c
Nothing -> newChan
forkIO (catch (newUser handle wh ch2)
(\_ -> hClose handle))
loop s (Just ch2)
• Fehlerbehandlung für newUser
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Längeres Beispiel: It’s good to talk
76
Talk 0.2: Benutzerprozeß
newUser :: Handle-> String-> Chan String -> IO ()
newUser socket wh msgch =
do hPutStrLn socket "Hello there. Please send your nickname.
nick <- do nm <- hGetLine socket
return (filter (not . isControl) nm)
hPutStrLn socket ("Nice to meet you!")
writeChan msgch (nick ++ "@" ++ wh ++ " has joined.")
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Längeres Beispiel: It’s good to talk
77
Talk 0.2: Benutzerprozeß
wp <- forkIO write
catch (read ((nick ++ ": ")++))
(\e-> do killThread wp
if isEOFError e then
writeChan msgch (nick++ "@"++ wh++ " has
else writeChan msgch (nick++ "@"++ wh++
" left hastily ("++ ioeGetErrorStri
hClose socket) where
read :: (String-> String)-> IO ()
read f = hGetLine socket >>= writeChan msgch. f >> read f
write :: IO ()
write = readChan msgch >>= hPutStrLn socket >> write
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
Längeres Beispiel: It’s good to talk
Talk 0.2: Zusammenfassung
Nachteile:
• Kein Nachrichtenstau mehr
• Fehlerbehandlung für Benutzerprozeß
• Anmeldeprozedur: Benutzer hat Namen
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
78
Längeres Beispiel: It’s good to talk
Talk 0.2: Zusammenfassung
Nachteile:
• Kein Nachrichtenstau mehr
• Fehlerbehandlung für Benutzerprozeß
• Anmeldeprozedur: Benutzer hat Namen
• Schnell verkaufen!
Christoph Lüth: Funktionale Systemprogrammierung, 3. Mai 2002
78
Herunterladen