Fortgeschrittene Techniken der Funktionalen Programmierung Vorlesung vom 01.12.09:
Grundlagen der Nebenl¨ aufigkeit in Haskell
Christoph L¨uth, Dennis Walter Universit¨at Bremen Wintersemester 2009/10
1
Fahrplan
ITeil I: Monaden und fortgeschrittene Typen ITeil II: Fortgeschrittene Datenstrukturen ITeil III: Nebenl¨aufigkeit
I Grundlagen
I Abstraktionen und Ausnahmebehandlung
I Software Transactional Memory ITeil IV: The Future of Programming
2
Heute gibt’s hier
Nebenl¨aufigkeit
IGrundkonzepte
IImplementation in Haskell
IBasiskonzepte
3
Konzepte der Nebenl¨ aufigkeit
I Thread (lightweight process) vs. Prozess Programmiersprache/Betriebssystem Betriebssystem (z.B. Java, Haskell, Linux)
gemeinsamerSpeicher getrennterSpeicher
Erzeugungbillig Erzeugungteuer
mehrereproProgramm einerproProgramm
IMultitasking:
I pr¨aemptiv:Kontextwechsel wirderzwungen
I kooperativ:Kontextwechsel nurfreiwillig
4
Zur Erinnerung: Threads in Java
IErweiterung der KlassenThreadoderRunnable IGestartet wird Methoderun()— durch eigene ¨uberladen IStarten des Threads durch Aufruf der Methodestart() IKontextwechsel mityield()
IJe nach JVM kooperativoderpr¨aemptiv.
ISynchronisation mitsynchronize
5
Threads in Haskell: Concurrent Haskell
ISequentiellesHaskell: Reduktion eines Ausdrucks
I Compiler legt Reihenfolge fes (outermost leftmost — verz¨ogerte Auswertung)
INebenl¨aufigesHaskell: Reduktion eines Ausdrucks anmehreren Stellen Ighcundhugsimplementieren Haskell-Threads
Ighc:pr¨aemptiv,hugs:kooperativ
IModulControl.Concurrententh¨alt Basisfunktionen IWenige Basisprimitive, darauf aufbauend Abstraktionen
6
Wesentliche Typen und Funktionen
IJeder Thread hat einen Identifier: abstrakter TypThreadId INeuen Thread erzeugen:forkIO:: IO()-¿ IO ThreadId IThread stoppen:killThread :: ThreadId -¿ IO () IKontextwechsel:yield :: IO ()
IEigener Thread:myThreadId :: IO ThreadId IWarten:threadDelay :: Int -¿ IO ()
7
Rahmenbedingungen
IZeitscheiben:
I Tick: Default 20ms
I Contextswitchpro Tick bei Heapallokation
I ¨Anderungen perKommandozeilenoptionen:+RTS -V¡time¿ -C¡time¿
IBlockierung:
I Systemaufrufe blockierenalle Threads
I Mit threaded library (-threaded) nicht alle
I Aber: Haskell Standard-IO blockiertnur den aufrufenden Thread
8
Concurrent Haskell — erste Schritte
IEin einfaches Beispiel:
w r i t e : : Char→ IO ( )
w r i t e c = putChar c w r i t e c main : : IO ( )
main = f o r k I O ( w r i t e ’X ’ ) w r i t e ’O’
IAusgabeghc: (X∗|O∗)∗ IAusgabehugs: (X∗|O∗)
9
Synchronisation mit MVars
IBasissynchronisationmechanismusin Concurrent Haskell
I Alles andereabgeleitet
IMVar aver¨anderbareVariable (vgl.IORef a) IEntwederleerodergef¨ulltmit Wert vom Typa IVerhalten beim Lesen und Schreiben
Zustand vorher: leer gef¨ullt
Lesen blockiert(bis gef¨ullt) danach leer Schreiben danach gef¨ullt blockiert(bis leer)
I NB.Aufweckenblockierter ProzesseeinzelninFIFO
10
Basisfunktionen MVars
INeue Variable erzeugen (leer oder gef¨ullt):
newEmptyMVar : : IO (MVar a ) newMVar : : a → IO (MVar a )
ILesen:
takeMVar : : MVar a → IO a
ISchreiben:
putMVar : : MVar a → a → IO ( )
11
Abgeleitete Funktionen MVars
INicht-blockierendes Lesen/Schreiben:
tryTakeMVar : : MVar a → IO ( Maybe a ) tryPutMVar : : MVar a→ a→ IO Bool
I ¨Anderung der MVar:
swapMVar : : MVar a → a → IO a
withMVar : : MVar a → ( a → IO b ) → IO b modifyMVar : : MVar a → ( a → IO ( a , b ) ) → IO b
I Achtung:race conditions
12
Ein einfaches Beispiel ohne Synchronisation
INebenl¨aufige Eingabe von der Tastatur
import C o n t r o l . Monad( f o r e v e r , r e p l i c a t e M ) import C o n t r o l . Concurrent
echo : : S t r i n g→ IO ( ) echo p = f o r e v e r $ do
putStrLn ( ” ∗∗∗ P l e a s e e n t e r l i n e f o r ”++p ) l i n e ← g e t L i n e
n←randomRIO (1 ,100)
r e p l i c a t e M n $ p u t S t r ( p++ ” : ”++ l i n e++” ” ) main : : IO ( )
main = f o r k I O ( echo ”2” ) echo ”1”
IProblem: gleichzeitige Eingabe IL¨osung:MVarsynchronisiert Eingabe
13
Ein einfaches Beispiel mit Synchronisation
IMVarvoll⇔Eingabe m¨oglich
I Also: initial voll
IInhalt der MVar irrelevant:MVar () echo : : MVar ( )→ S t r i n g→ IO ( ) echo f l a g p = f o r e v e r $ do
takeMVar f l a g
putStrLn ( ” ∗∗∗ P l e a s e e n t e r l i n e ”++ p ) l i n e ← g e t L i n e
n←randomRIO (1 ,100)
r e p l i c a t e M n $ p u t S t r ( p++ ” : ”++ l i n e++” ” ) putMVar f l a g ( )
main : : IO ( )
main = do f l a g ←newMVar ( )
f o r k I O ( echo f l a g ”3” ) f o r k I O ( echo f l a g ”2” ) echo f l a g ”1”
14
Das Standardbeispiel
ISpeisende Philosopen
IPhilosophi:
Ivor dem Esseni-tes und(i+1)modn-tes St¨abchen nehmen
Inach dem Essen wieder zur¨ucklegen
ISt¨abchen modelliert alsMVar ()
15
Speisende Philosophen
p h i l o : : [ MVar ( ) ] → I n t→ IO ( ) p h i l o c h o p s t i c k s i = f o r e v e r $ do
l e t num phil = l e n g t h ( c h o p s t i c k s )
−−Thinking:
putStrLn ( ” P h i l #”++ show i ++” t h i n k s . . . ” ) randomRIO (10 , 200)=t h r e a d D e l a y
−−Get ready to eat:
takeMVar ( c h o p s t i c k s ! ! i )
takeMVar ( c h o p s t i c k s ! ! ( ( i +1) ‘mod ‘ num phil ) )
−−Eat:
putStrLn ( ” P h i l #”++ show i ++” e a t s . . . ” ) randomRIO (10 , 200)=t h r e a d D e l a y
−−Done eating:
putMVar ( c h o p s t i c k s ! ! i ) ( )
putMVar ( c h o p s t i c k s ! ! ( ( i +1) ‘mod ‘ num phil ) ) ( )
16
Speisende Philosophen
IHauptfunktion:nSt¨abchen erzeugen IAnzahl Philosophen in der Kommandozeile
main = do a : ← getArgs l e t num= read a
c h o p s t i c k s ← r e p l i c a t e M num $ newMVar ( ) mapM ( f o r k I O . ( p h i l o c h o p s t i c k s ) ) [ 0 . . num−1]
b l o c k
IHilfsfunktionblock: blockiert aufrufenden Thread b l o c k : : IO ( )
b l o c k = newEmptyMVar=takeMVar INB: Hauptthread terminiert — Programm terminiert!
17
Abstraktion: Semaphoren
IAbstrakter DatentypQSem
IBetretenkritischer Abschnitt(P):waitQSem :: QSem→IO () IVerlassenkritischer Abschnitt(V):signalQSem :: QSem→IO () ISemaphore: Z¨ahler plus evtl. wartende Threads
I Perniedrigt Z¨ahler, blockiert ggf. aufrufenden Thread
I Verh¨oht Z¨ahler, gibt ggf. blockierte Threads frei
IImplementierung von Semaphoren mitMVar: eigenes Scheduling IVariation:Quantitative Semaphoren
I Z¨ahler kann um Parameternerh¨oht/erniedrigt werden
18
Semaphoren: die P-Operation
data QSem = QSem (MVar ( Int , [ MVar ( ) ] ) ) IMVar ..f¨ur die ganze Semaphore, darin:
IZ¨ahler der Prozesse im kritischen Abschnitt
IListe von wartenden Prozessen (MVar ()) newQSem : : I n t → IO QSem
newQSem n = do m←newMVar ( n , [] ) r e t u r n (QSem m)
19
Semaphoren: die P-Operation
IEintrittin kritischen Abschnitt
IWenn Eintritt m¨oglich, Z¨ahler erniedrigen IAnsonsten blockieren (Reihenfolge!) waitQSem : : QSem → IO ( ) waitQSem (QSem sem ) = do
( a v a i l , b l o c k e d )← takeMVar sem i f a v a i l > 0 then
putMVar sem ( a v a i l−1, [] ) e l s e do
b l o c k ←newEmptyMVar
putMVar sem (0 , b l o c k e d++ [ b l o c k ] ) takeMVar b l o c k
20
Semaphoren: die V-Operation
IVerlassendes kritischen Abschnitts IFalls wartende threads, einen aufwecken.
IAlternatives Scheduling:
Iam Anfang hinzuf¨ugen, vom Anfang nehmen (einfacher,unfair)
Iam besten:zuf¨alligeAuswahl signalQSem : : QSem → IO ( ) signalQSem (QSem sem ) = do
( a v a i l , b l o c k e d ) ←takeMVar sem c a s e b l o c k e d o f
[] → putMVar sem ( a v a i l +1, [] ) b l o c k : blocked ’ → do
putMVar sem (0 , blocked ’ ) putMVar b l o c k ( )
21
Zusammenfassung
IConcurrent Haskellbietet
I Threadsauf Quellsprachenebene
I Synchronisierung mitMVars
I Durchschlankes Designeinfache Implementierung IFunktionales Paradigma erlaubtAbstraktionen
I Beispiel:Semaphoren
IN¨achste Woche:Kan¨aleundAusnahmen.
22