summaryrefslogtreecommitdiff
path: root/ws2015/ffp/blaetter/09/FFP_U09_Concurrent.hs
blob: 99026c06b66b65e785dc558f97bbf0f0c27543f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
-- Fortgeschrittene Funktionale Programmierung, 
--   LMU, TCS, Wintersemester 2015/16

--   Steffen Jost, Alexander Isenko
--
-- Übungsblatt 09. 16.12.2015
--
-- Thema: Ausnahmen & Nebenläufigkeit
--
-- Hinweis:
--   Für diese Übung ist ein Rechner mit mehreren Kernen
--   nützlich, aber nicht zwingend notwendig.
--   Wenn Sie möchten, dann Benutzen Sie einfach einen 
--   Mehrkern-Rechner am CIP-Pool der Informatik. Hinweise
--   zum Remote-Login am CIP-Pool finden Sie am Anfang des 
--   vorherigen Übungsblattes.
-- 
--   
-- Anweisungen:  
--
--   Gehen Sie diese Datei durch und bearbeiten Sie 
--   alle Vorkommen von undefined bzw. die mit -- !!! TODO !!!
--   markierten Stellen. Testen Sie Ihre Lösungen mit GHC!
--  


import Data.List
import Data.Typeable
import Control.Exception
import Control.Monad
import Control.Concurrent
import Control.DeepSeq

import Data.Ord (comparing)

import System.Environment (getArgs)

-- Zum Umschalten zwischen den Aufgaben umkommentieren,
-- oder besser die Aufgaben in einzelne Dateien auftrennen.
main = main' [ ("1", main_A8_1)
             , ("2", main_A8_2)
             , ("3", main_A8_3)
             ]

main' :: [(String, IO ())] -> IO ()
main' as = do
  args <- getArgs
  let
    handler (x, a)
      | x `elem` args = a
      | otherwise = return ()
  mapM_ handler as


-- A8-1 Ausnahmen
--
-- a) 
-- Definieren Sie eine Funktion "myHead",
-- welche wie "head" aus der Standardbibliothek funktioniert,
-- aber stattdessen eine von Ihnen selbst-definierte
-- Ausnahme wirft. 
-- 

data EmptyListException = EmptyListException
                        deriving (Show, Typeable)

instance Exception EmptyListException

myHead :: [a] -> a
myHead [] = throw EmptyListException
myHead (a:_) = a

main_A8_1 :: IO ()
main_A8_1 = do
    putStrLn "Jetzt sollten drei Werte kommen, aber keine Exception:"
    print $ myHead     [1,2,undefined] -- Zeile nicht ändern! 
    h1 <- mySafeHead [3,4,undefined]   -- Zeile nicht ändern!
    print h1
    h2 <- mySafeHead empty
    print h2
    putStrLn "Jetzt sollte gleich eine benutzerdefinierte Exception kommen:"
    print $ myHead   empty
  where
      empty = [] :: [Int] -- Typangabe für leere Liste, 
                          -- damit Show-Instance festgelegt wird.
                          -- HINWEIS: 
                          -- Das ist gar nicht albern, denn 
                          -- "print ([] :: String)" soll ja etwas
                          -- anderes ausgeben als
                          -- "print ([] :: [Int])"!
  
  
-- b) 
-- Implementieren Sie "mySafeHead" unter Verwendung
-- von "myHead", indem Sie die Ausnahme abfangen und behandeln.
--
-- Hinweis: 
-- Ausnahmen können nur innerhalb der IO-Monade gefangen werden.


mySafeHead :: [a] -> IO (Maybe a)
-- mySafeHead [] = return Nothing -- Gilt hier nicht als Lösung! Zu Übungszwecken bitte myHead mit catch/try einsetzen!
mySafeHead = handle (\EmptyListException -> return Nothing) . (fmap Just) . evaluate . myHead
    

    
    
-- A8-2 Paralleles Rechnen mit Nebenläufigkeit
--
-- Auch wenn es eigentlich nicht der Sinn der Sache ist:
-- Nebenläufigkeit kann ebenfalls dazu verwendet werden,
-- die Ausführung eines Programmes durch Ausnutzung mehrerer
-- Prozessorkerne zu beschleunigen.
--
-- Das folgende Programm führt vier unterschiedlich aufwändige 
-- Berechnungen durch (Aufgabenstellung folgt weiter unten):

main_A8_2 :: IO ()
main_A8_2 = main' [ ("seq", main_A8_2_seq)
                  , ("a", main_A8_2_a)
                  , ("b", main_A8_2_b)
                  ]

type Task = Integer -> [String]

main_A8_2_seq :: IO ()
main_A8_2_seq = do 
  putStrLn "Ein paar interessante Fakten:"
  putStrLn " "
  mapM_ putStrLn $ taskA nA
  mapM_ putStrLn $ taskB nB
  mapM_ putStrLn $ taskC nC
  mapM_ putStrLn $ taskD nD
  putStrLn " "
  putStrLn "Höchst interessant, oder?"
             
    
-- Hilfsfunktionen, zur Erzeugung von Rechnenlast,
-- zur Lösung der Aufgabe sind diese unbedeutend.
--
-- HINWEIS:
-- Die Werte sind zur Ausführung mit GHC -O2 auf aktueller Hardware gedacht
-- Bei Ausführung in GHCI sind die Werte anzupassen (halbieren?).
-- Idealerweise sollte jeder Task ca. 10 Sekunden Laufzeit benötigen.
  
nA = 14
  
taskA :: Task
taskA n = ["* Ein Hanoi-Turm der Höhe " ++ (show n)
          ,"  braucht genau " ++ (show steps) ++ " Schritte zum versetzen." ]
  where
    steps = length $ hanoi n 1 3

hanoi :: (Num a, Eq a) => a -> a -> a -> [(a,a)]          
hanoi 1 i j = [(i,j)]
hanoi n i j = 
       hanoi n' i otherTower
    ++ [(i,j)] 
    ++ hanoi n' otherTower j
  where
    n'         = n-1
    otherTower = 1+2+3-i-j
        
nB = 30
  
taskB :: Task
taskB n = ["* Die " ++ (show n) ++ ". Fibonacci-Zahl" 
          ,"  lautet: " ++ (show fn) ]
  where
    fn = fib n

fib :: (Ord a, Num a, Num b) => a -> b
fib 0 = 0 -- absichtlich langsame Berechnung
fib n | n <= 1    = 1
      | otherwise = fib (n-1) + fib (n-2)
    
nC = 11728
    
taskC :: Task
taskC n = ["* Die Fakultät von " ++ (show n)
          ,"  ist eine Zahl mit " ++ (show l) ++ " Stellen." ]
  where
    fn = product [1..n]
    l  = length $ show fn

nD = 279
    
taskD :: Task
taskD n = ["* Die Anzahl der Collatz-Schritte von " ++ (show n) ++ " bis 1 "
          ,"  beträgt genau " ++ (show cn)] 
  where
    cn = numCSteps n
    
    
collatzStep :: Integral a => a -> a
collatzStep n 
  | even n    = n `div` 2
  | otherwise = 3*n+1    

numCSteps :: Integral a => a -> a  
numCSteps n = ncs 0 n
  where -- Hinweis: Es ist unbekannt, ob es immer terminiert.
    ncs steps 1 = steps
    ncs steps m = ncs (succ steps) (collatzStep m)
    
  
-- a) 
-- Verändern Sie das gegebene Hauptprogramm so, dass 
-- die vier Ergebnisse so schnell wie möglich ausgegeben werden.
-- Das am schnellsten zu berechnende Ergebnis soll also zuerst ausgegeben werden,
-- das am langsamsten zu berechnende Ergebnis am Ende.
-- Die einzelnen Zeilen einer Aufgabe sollen auch zusammen ausgegeben werden!
--
-- Hinweis: 
-- Sie sehen zwar keine Beschleunigung, wenn Sie nur einen Prozessorkern 
-- zur Verfügung haben, aber Sie sehen trotzdem, dass 
-- die leichteste Aufgabe (taskD) als erste ausgegeben wird,
-- wenn Sie die Aufgabe richtig gelöst haben!

main_A8_2_a :: IO ()
main_A8_2_a = do
  putStrLn "Ein paar interessante Fakten:"
  putStrLn " "
  runPar [ taskA nA
         , taskB nB
         , taskC nC
         , taskD nD
         ]
  putStrLn " "
  putStrLn "Höchst interessant, oder?"
  where
    runPar as = do
      rChan <- newChan
      mapM_ (forkIO . (writeChan rChan $!!)) as
      replicateM_ (length as) (readChan rChan >>= mapM_ putStrLn)
    
  
-- b)  
-- Verändern Sie das Hauptprogramm so, dass die Berechnungen
-- zwar parallel ausgeführt werden, aber das die gesamte Ausgabe
-- in einem Schritt nach Ende der langsamsten Berechnung erfolgt,
-- in der gleichen Reihenfolge des ursprünglichen Programmes.

main_A8_2_b :: IO ()
main_A8_2_b = do
  putStrLn "Ein paar interessante Fakten:"
  putStrLn " "
  runPar [ taskA nA
         , taskB nB
         , taskC nC
         , taskD nD
         ]
  putStrLn " "
  putStrLn "Höchst interessant, oder?"
  where
    runPar as = do
      rChan <- newChan
      mapM_ (forkIO . (writeChan rChan $!!)) as'
      rs <- replicateM (length as) (readChan rChan)
      mapM_ putStrLn . join $ map snd $ sortBy (comparing fst) rs
      where
        as' = zip ([0..] :: [Integer]) as
  
  
  
-- A8-3 Dead Locks und Race-Conditions
--
-- Was passiert in folgendem Programm?
-- Wieso kommt es zu einem Dead-Lock?
-- Überlegen Sie sich Möglichkeiten, dies zu vermeiden!

{-
  Neither `md` nor `workerC` ever actually get used

  It suffices to show that a scheduling exists for which
  a deadlock occurs.

  Consider: A.1, B.1, B'.1, A.2, B.2, B'.2


  To prevent deadlocks with only minimal changes to semantics
  consider using one `MVar (Integer, Integer, Integer)`.

  However in the authors opinion considerable changes to the
  semantics are preferable.
-}

main_A8_3 :: IO ()
main_A8_3 = do
  ma <- newMVar 0
  mb <- newMVar 0
  mc <- newMVar 0
  md <- newMVar 0
  forkIO $ forever $ workerA ma mb -- thread A
  forkIO $ forever $ workerB mb mc -- thread B
  forkIO $ forever $ workerB mc ma -- thread B'
  replicateM_ 1000 $ do
    a <- readMVar ma    
    b <- readMVar mb     
    c <- readMVar mc
    d <- readMVar md
    print (a,b,c,d)
    threadDelay 4000

workerA :: MVar Integer -> MVar Integer -> IO ()
workerA mx my = do
  x <- takeMVar mx -- A.1
  y <- takeMVar my -- A.2
  let x' = x+1    
  let y' = y-1
  putMVar my $! y'
  putMVar mx $! x'

workerB :: MVar Integer -> MVar Integer -> IO ()
workerB mx my = do 
  x <- takeMVar mx -- B.1 / B'.1
  y <- takeMVar my -- B.2 / B'.2
  putMVar mx $! collatzStep x
  putMVar my $! collatzStep y

workerC :: MVar Integer -> MVar Integer -> IO ()
workerC mx my = do 
  x <- takeMVar mx
  let x' = if x < 42 then fib x else 0
  putMVar mx $! x'
  y <- takeMVar my
  let y' = y + x'
  putMVar my $! y'