diff options
Diffstat (limited to 'ws2015/ffp')
-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' | ||