diff options
| author | Gregor Kleen <gkleen@yggdrasil.li> | 2015-12-23 20:45:54 +0100 | 
|---|---|---|
| committer | Gregor Kleen <gkleen@yggdrasil.li> | 2015-12-23 20:45:54 +0100 | 
| commit | 1d27f7b9aac4058f28aa8e56cb112ad1018614d7 (patch) | |
| tree | 0194af4bf77ec1bcfb8fb7ff4f698230b570fb61 | |
| parent | 5d9a2f27772eabdf3f5d0a53a533773925197f12 (diff) | |
| download | uni-1d27f7b9aac4058f28aa8e56cb112ad1018614d7.tar uni-1d27f7b9aac4058f28aa8e56cb112ad1018614d7.tar.gz uni-1d27f7b9aac4058f28aa8e56cb112ad1018614d7.tar.bz2 uni-1d27f7b9aac4058f28aa8e56cb112ad1018614d7.tar.xz uni-1d27f7b9aac4058f28aa8e56cb112ad1018614d7.zip | |
FFP 09
| -rw-r--r-- | ws2015/ffp/blaetter/09/FFP_U09_Concurrent.hs | 328 | 
1 files changed, 328 insertions, 0 deletions
| diff --git a/ws2015/ffp/blaetter/09/FFP_U09_Concurrent.hs b/ws2015/ffp/blaetter/09/FFP_U09_Concurrent.hs new file mode 100644 index 0000000..99026c0 --- /dev/null +++ b/ws2015/ffp/blaetter/09/FFP_U09_Concurrent.hs | |||
| @@ -0,0 +1,328 @@ | |||
| 1 | -- Fortgeschrittene Funktionale Programmierung, | ||
| 2 | -- LMU, TCS, Wintersemester 2015/16 | ||
| 3 | |||
| 4 | -- Steffen Jost, Alexander Isenko | ||
| 5 | -- | ||
| 6 | -- Übungsblatt 09. 16.12.2015 | ||
| 7 | -- | ||
| 8 | -- Thema: Ausnahmen & Nebenläufigkeit | ||
| 9 | -- | ||
| 10 | -- Hinweis: | ||
| 11 | -- Für diese Übung ist ein Rechner mit mehreren Kernen | ||
| 12 | -- nützlich, aber nicht zwingend notwendig. | ||
| 13 | -- Wenn Sie möchten, dann Benutzen Sie einfach einen | ||
| 14 | -- Mehrkern-Rechner am CIP-Pool der Informatik. Hinweise | ||
| 15 | -- zum Remote-Login am CIP-Pool finden Sie am Anfang des | ||
| 16 | -- vorherigen Übungsblattes. | ||
| 17 | -- | ||
| 18 | -- | ||
| 19 | -- Anweisungen: | ||
| 20 | -- | ||
| 21 | -- Gehen Sie diese Datei durch und bearbeiten Sie | ||
| 22 | -- alle Vorkommen von undefined bzw. die mit -- !!! TODO !!! | ||
| 23 | -- markierten Stellen. Testen Sie Ihre Lösungen mit GHC! | ||
| 24 | -- | ||
| 25 | |||
| 26 | |||
| 27 | import Data.List | ||
| 28 | import Data.Typeable | ||
| 29 | import Control.Exception | ||
| 30 | import Control.Monad | ||
| 31 | import Control.Concurrent | ||
| 32 | import Control.DeepSeq | ||
| 33 | |||
| 34 | import Data.Ord (comparing) | ||
| 35 | |||
| 36 | import System.Environment (getArgs) | ||
| 37 | |||
| 38 | -- Zum Umschalten zwischen den Aufgaben umkommentieren, | ||
| 39 | -- oder besser die Aufgaben in einzelne Dateien auftrennen. | ||
| 40 | main = main' [ ("1", main_A8_1) | ||
| 41 | , ("2", main_A8_2) | ||
| 42 | , ("3", main_A8_3) | ||
| 43 | ] | ||
| 44 | |||
| 45 | main' :: [(String, IO ())] -> IO () | ||
| 46 | main' as = do | ||
| 47 | args <- getArgs | ||
| 48 | let | ||
| 49 | handler (x, a) | ||
| 50 | | x `elem` args = a | ||
| 51 | | otherwise = return () | ||
| 52 | mapM_ handler as | ||
| 53 | |||
| 54 | |||
| 55 | -- A8-1 Ausnahmen | ||
| 56 | -- | ||
| 57 | -- a) | ||
| 58 | -- Definieren Sie eine Funktion "myHead", | ||
| 59 | -- welche wie "head" aus der Standardbibliothek funktioniert, | ||
| 60 | -- aber stattdessen eine von Ihnen selbst-definierte | ||
| 61 | -- Ausnahme wirft. | ||
| 62 | -- | ||
| 63 | |||
| 64 | data EmptyListException = EmptyListException | ||
| 65 | deriving (Show, Typeable) | ||
| 66 | |||
| 67 | instance Exception EmptyListException | ||
| 68 | |||
| 69 | myHead :: [a] -> a | ||
| 70 | myHead [] = throw EmptyListException | ||
| 71 | myHead (a:_) = a | ||
| 72 | |||
| 73 | main_A8_1 :: IO () | ||
| 74 | main_A8_1 = do | ||
| 75 | putStrLn "Jetzt sollten drei Werte kommen, aber keine Exception:" | ||
| 76 | print $ myHead [1,2,undefined] -- Zeile nicht ändern! | ||
| 77 | h1 <- mySafeHead [3,4,undefined] -- Zeile nicht ändern! | ||
| 78 | print h1 | ||
| 79 | h2 <- mySafeHead empty | ||
| 80 | print h2 | ||
| 81 | putStrLn "Jetzt sollte gleich eine benutzerdefinierte Exception kommen:" | ||
| 82 | print $ myHead empty | ||
| 83 | where | ||
| 84 | empty = [] :: [Int] -- Typangabe für leere Liste, | ||
| 85 | -- damit Show-Instance festgelegt wird. | ||
| 86 | -- HINWEIS: | ||
| 87 | -- Das ist gar nicht albern, denn | ||
| 88 | -- "print ([] :: String)" soll ja etwas | ||
| 89 | -- anderes ausgeben als | ||
| 90 | -- "print ([] :: [Int])"! | ||
| 91 | |||
| 92 | |||
| 93 | -- b) | ||
| 94 | -- Implementieren Sie "mySafeHead" unter Verwendung | ||
| 95 | -- von "myHead", indem Sie die Ausnahme abfangen und behandeln. | ||
| 96 | -- | ||
| 97 | -- Hinweis: | ||
| 98 | -- Ausnahmen können nur innerhalb der IO-Monade gefangen werden. | ||
| 99 | |||
| 100 | |||
| 101 | mySafeHead :: [a] -> IO (Maybe a) | ||
| 102 | -- mySafeHead [] = return Nothing -- Gilt hier nicht als Lösung! Zu Übungszwecken bitte myHead mit catch/try einsetzen! | ||
| 103 | mySafeHead = handle (\EmptyListException -> return Nothing) . (fmap Just) . evaluate . myHead | ||
| 104 | |||
| 105 | |||
| 106 | |||
| 107 | |||
| 108 | -- A8-2 Paralleles Rechnen mit Nebenläufigkeit | ||
| 109 | -- | ||
| 110 | -- Auch wenn es eigentlich nicht der Sinn der Sache ist: | ||
| 111 | -- Nebenläufigkeit kann ebenfalls dazu verwendet werden, | ||
| 112 | -- die Ausführung eines Programmes durch Ausnutzung mehrerer | ||
| 113 | -- Prozessorkerne zu beschleunigen. | ||
| 114 | -- | ||
| 115 | -- Das folgende Programm führt vier unterschiedlich aufwändige | ||
| 116 | -- Berechnungen durch (Aufgabenstellung folgt weiter unten): | ||
| 117 | |||
| 118 | main_A8_2 :: IO () | ||
| 119 | main_A8_2 = main' [ ("seq", main_A8_2_seq) | ||
| 120 | , ("a", main_A8_2_a) | ||
| 121 | , ("b", main_A8_2_b) | ||
| 122 | ] | ||
| 123 | |||
| 124 | type Task = Integer -> [String] | ||
| 125 | |||
| 126 | main_A8_2_seq :: IO () | ||
| 127 | main_A8_2_seq = do | ||
| 128 | putStrLn "Ein paar interessante Fakten:" | ||
| 129 | putStrLn " " | ||
| 130 | mapM_ putStrLn $ taskA nA | ||
| 131 | mapM_ putStrLn $ taskB nB | ||
| 132 | mapM_ putStrLn $ taskC nC | ||
| 133 | mapM_ putStrLn $ taskD nD | ||
| 134 | putStrLn " " | ||
| 135 | putStrLn "Höchst interessant, oder?" | ||
| 136 | |||
| 137 | |||
| 138 | -- Hilfsfunktionen, zur Erzeugung von Rechnenlast, | ||
| 139 | -- zur Lösung der Aufgabe sind diese unbedeutend. | ||
| 140 | -- | ||
| 141 | -- HINWEIS: | ||
| 142 | -- Die Werte sind zur Ausführung mit GHC -O2 auf aktueller Hardware gedacht | ||
| 143 | -- Bei Ausführung in GHCI sind die Werte anzupassen (halbieren?). | ||
| 144 | -- Idealerweise sollte jeder Task ca. 10 Sekunden Laufzeit benötigen. | ||
| 145 | |||
| 146 | nA = 14 | ||
| 147 | |||
| 148 | taskA :: Task | ||
| 149 | taskA n = ["* Ein Hanoi-Turm der Höhe " ++ (show n) | ||
| 150 | ," braucht genau " ++ (show steps) ++ " Schritte zum versetzen." ] | ||
| 151 | where | ||
| 152 | steps = length $ hanoi n 1 3 | ||
| 153 | |||
| 154 | hanoi :: (Num a, Eq a) => a -> a -> a -> [(a,a)] | ||
| 155 | hanoi 1 i j = [(i,j)] | ||
| 156 | hanoi n i j = | ||
| 157 | hanoi n' i otherTower | ||
| 158 | ++ [(i,j)] | ||
| 159 | ++ hanoi n' otherTower j | ||
| 160 | where | ||
| 161 | n' = n-1 | ||
| 162 | otherTower = 1+2+3-i-j | ||
| 163 | |||
| 164 | nB = 30 | ||
| 165 | |||
| 166 | taskB :: Task | ||
| 167 | taskB n = ["* Die " ++ (show n) ++ ". Fibonacci-Zahl" | ||
| 168 | ," lautet: " ++ (show fn) ] | ||
| 169 | where | ||
| 170 | fn = fib n | ||
| 171 | |||
| 172 | fib :: (Ord a, Num a, Num b) => a -> b | ||
| 173 | fib 0 = 0 -- absichtlich langsame Berechnung | ||
| 174 | fib n | n <= 1 = 1 | ||
| 175 | | otherwise = fib (n-1) + fib (n-2) | ||
| 176 | |||
| 177 | nC = 11728 | ||
| 178 | |||
| 179 | taskC :: Task | ||
| 180 | taskC n = ["* Die Fakultät von " ++ (show n) | ||
| 181 | ," ist eine Zahl mit " ++ (show l) ++ " Stellen." ] | ||
| 182 | where | ||
| 183 | fn = product [1..n] | ||
| 184 | l = length $ show fn | ||
| 185 | |||
| 186 | nD = 279 | ||
| 187 | |||
| 188 | taskD :: Task | ||
| 189 | taskD n = ["* Die Anzahl der Collatz-Schritte von " ++ (show n) ++ " bis 1 " | ||
| 190 | ," beträgt genau " ++ (show cn)] | ||
| 191 | where | ||
| 192 | cn = numCSteps n | ||
| 193 | |||
| 194 | |||
| 195 | collatzStep :: Integral a => a -> a | ||
| 196 | collatzStep n | ||
| 197 | | even n = n `div` 2 | ||
| 198 | | otherwise = 3*n+1 | ||
| 199 | |||
| 200 | numCSteps :: Integral a => a -> a | ||
| 201 | numCSteps n = ncs 0 n | ||
| 202 | where -- Hinweis: Es ist unbekannt, ob es immer terminiert. | ||
| 203 | ncs steps 1 = steps | ||
| 204 | ncs steps m = ncs (succ steps) (collatzStep m) | ||
| 205 | |||
| 206 | |||
| 207 | -- a) | ||
| 208 | -- Verändern Sie das gegebene Hauptprogramm so, dass | ||
| 209 | -- die vier Ergebnisse so schnell wie möglich ausgegeben werden. | ||
| 210 | -- Das am schnellsten zu berechnende Ergebnis soll also zuerst ausgegeben werden, | ||
| 211 | -- das am langsamsten zu berechnende Ergebnis am Ende. | ||
| 212 | -- Die einzelnen Zeilen einer Aufgabe sollen auch zusammen ausgegeben werden! | ||
| 213 | -- | ||
| 214 | -- Hinweis: | ||
| 215 | -- Sie sehen zwar keine Beschleunigung, wenn Sie nur einen Prozessorkern | ||
| 216 | -- zur Verfügung haben, aber Sie sehen trotzdem, dass | ||
| 217 | -- die leichteste Aufgabe (taskD) als erste ausgegeben wird, | ||
| 218 | -- wenn Sie die Aufgabe richtig gelöst haben! | ||
| 219 | |||
| 220 | main_A8_2_a :: IO () | ||
| 221 | main_A8_2_a = do | ||
| 222 | putStrLn "Ein paar interessante Fakten:" | ||
| 223 | putStrLn " " | ||
| 224 | runPar [ taskA nA | ||
| 225 | , taskB nB | ||
| 226 | , taskC nC | ||
| 227 | , taskD nD | ||
| 228 | ] | ||
| 229 | putStrLn " " | ||
| 230 | putStrLn "Höchst interessant, oder?" | ||
| 231 | where | ||
| 232 | runPar as = do | ||
| 233 | rChan <- newChan | ||
| 234 | mapM_ (forkIO . (writeChan rChan $!!)) as | ||
| 235 | replicateM_ (length as) (readChan rChan >>= mapM_ putStrLn) | ||
| 236 | |||
| 237 | |||
| 238 | -- b) | ||
| 239 | -- Verändern Sie das Hauptprogramm so, dass die Berechnungen | ||
| 240 | -- zwar parallel ausgeführt werden, aber das die gesamte Ausgabe | ||
| 241 | -- in einem Schritt nach Ende der langsamsten Berechnung erfolgt, | ||
| 242 | -- in der gleichen Reihenfolge des ursprünglichen Programmes. | ||
| 243 | |||
| 244 | main_A8_2_b :: IO () | ||
| 245 | main_A8_2_b = do | ||
| 246 | putStrLn "Ein paar interessante Fakten:" | ||
| 247 | putStrLn " " | ||
| 248 | runPar [ taskA nA | ||
| 249 | , taskB nB | ||
| 250 | , taskC nC | ||
| 251 | , taskD nD | ||
| 252 | ] | ||
| 253 | putStrLn " " | ||
| 254 | putStrLn "Höchst interessant, oder?" | ||
| 255 | where | ||
| 256 | runPar as = do | ||
| 257 | rChan <- newChan | ||
| 258 | mapM_ (forkIO . (writeChan rChan $!!)) as' | ||
| 259 | rs <- replicateM (length as) (readChan rChan) | ||
| 260 | mapM_ putStrLn . join $ map snd $ sortBy (comparing fst) rs | ||
| 261 | where | ||
| 262 | as' = zip ([0..] :: [Integer]) as | ||
| 263 | |||
| 264 | |||
| 265 | |||
| 266 | -- A8-3 Dead Locks und Race-Conditions | ||
| 267 | -- | ||
| 268 | -- Was passiert in folgendem Programm? | ||
| 269 | -- Wieso kommt es zu einem Dead-Lock? | ||
| 270 | -- Überlegen Sie sich Möglichkeiten, dies zu vermeiden! | ||
| 271 | |||
| 272 | {- | ||
| 273 | Neither `md` nor `workerC` ever actually get used | ||
| 274 | |||
| 275 | It suffices to show that a scheduling exists for which | ||
| 276 | a deadlock occurs. | ||
| 277 | |||
| 278 | Consider: A.1, B.1, B'.1, A.2, B.2, B'.2 | ||
| 279 | |||
| 280 | |||
| 281 | To prevent deadlocks with only minimal changes to semantics | ||
| 282 | consider using one `MVar (Integer, Integer, Integer)`. | ||
| 283 | |||
| 284 | However in the authors opinion considerable changes to the | ||
| 285 | semantics are preferable. | ||
| 286 | -} | ||
| 287 | |||
| 288 | main_A8_3 :: IO () | ||
| 289 | main_A8_3 = do | ||
| 290 | ma <- newMVar 0 | ||
| 291 | mb <- newMVar 0 | ||
| 292 | mc <- newMVar 0 | ||
| 293 | md <- newMVar 0 | ||
| 294 | forkIO $ forever $ workerA ma mb -- thread A | ||
| 295 | forkIO $ forever $ workerB mb mc -- thread B | ||
| 296 | forkIO $ forever $ workerB mc ma -- thread B' | ||
| 297 | replicateM_ 1000 $ do | ||
| 298 | a <- readMVar ma | ||
| 299 | b <- readMVar mb | ||
| 300 | c <- readMVar mc | ||
| 301 | d <- readMVar md | ||
| 302 | print (a,b,c,d) | ||
| 303 | threadDelay 4000 | ||
| 304 | |||
| 305 | workerA :: MVar Integer -> MVar Integer -> IO () | ||
| 306 | workerA mx my = do | ||
| 307 | x <- takeMVar mx -- A.1 | ||
| 308 | y <- takeMVar my -- A.2 | ||
| 309 | let x' = x+1 | ||
| 310 | let y' = y-1 | ||
| 311 | putMVar my $! y' | ||
| 312 | putMVar mx $! x' | ||
| 313 | |||
| 314 | workerB :: MVar Integer -> MVar Integer -> IO () | ||
| 315 | workerB mx my = do | ||
| 316 | x <- takeMVar mx -- B.1 / B'.1 | ||
| 317 | y <- takeMVar my -- B.2 / B'.2 | ||
| 318 | putMVar mx $! collatzStep x | ||
| 319 | putMVar my $! collatzStep y | ||
| 320 | |||
| 321 | workerC :: MVar Integer -> MVar Integer -> IO () | ||
| 322 | workerC mx my = do | ||
| 323 | x <- takeMVar mx | ||
| 324 | let x' = if x < 42 then fib x else 0 | ||
| 325 | putMVar mx $! x' | ||
| 326 | y <- takeMVar my | ||
| 327 | let y' = y + x' | ||
| 328 | putMVar my $! y' | ||
