haskell concorrente - mcta016-13 - paradigmas de...

Post on 04-Aug-2020

3 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Haskell ConcorrenteMCTA016-13 - Paradigmas de Programação

Emilio Francesquinie.francesquini@ufabc.edu.br2019.Q2

Centro de Matemática, Computação e CogniçãoUniversidade Federal do ABC

Disclaimer

■ Estes slides foram preparados para o curso de Paradigmasde Programação na UFABC.

■ Este material pode ser usado livremente desde que sejammantidos, além deste aviso, os créditos aos autores einstituições.

■ Conteúdo baseado no texto preparado, e gentilmentecedido, pelo Professor Fabrício Olivetti de França daUFABC.

1

Haskell Concorrente

Programação Concorrente

O objetivo da programação concorrente é definirexplicitamente múltiplos caminhos de controle, geralmentepara permitir múltiplas interações com usuário ou interfacesexternas.

Não necessariamente serão executadas em paralelo.

2

Programação Concorrente

A ideia é descrever cada interação em separado, mas fazercom que elas ocorram ao mesmo tempo (intercaladamente).

3

Programação Concorrente

Considere um Web Browser que permite carregar múltiplaspáginas ao mesmo tempo.

Além disso, enquanto ele carrega uma ou mais páginas, ainterface ainda interage com o usuário (apertar o botãocancelar, etc.)

4

Programação Concorrente

Um servidor Web também implementa concorrência paraservir múltiplos pedidos ao mesmo tempo.

Imagine a situação de um servidor Web atendendo um pedidode página por vez.

5

Haskell Concorrente

O Haskell fornece um conjunto de funcionalidades simplesmas genéricas que podem ser utilizadas para definir estruturasmais complexas.

Com isso fica a cargo do programador definir o melhor modelode programação concorrente.

6

Conceitos Básicos

Criando uma thread

Uma thread é a menor sequência de computação que pode sergerenciada de forma independente pelo gerenciador detarefas.

Um processo é um procedimento computacional completo quepode conter diversas threads de execução.

7

Criando uma thread

Múltiplas threads de um mesmo processo compartilham amemória alocada, podendo utilizá-la para troca de mensagens.

Múltiplos processos não compartilham memória alocada.

8

Criando uma thread

No Haskell criamos uma thread com a função forkIO:

1 import Control.Concurrent2

3 forkIO :: IO () -> IO ThreadId

Ela recebe uma ação computacional de IO como argumento eretorna um identificador dessa thread.

9

Criando uma thread

A ideia é que todo efeito colateral feito pela ação IO será feitode forma concorrente com outras threads:

1 import Control.Concurrent2 import Control.Monad3 import System.IO4

5 main = do6 hSetBuffering stdout NoBuffering7 forkIO (replicateM_ 100000 (putChar 'A'))8 replicateM_ 100000 (putChar 'B')

10

Criando uma thread

A primeira linha da função main desativa o buffer paraexecutar toda ação IO no momento exato em que ela éenviada.

1 import Control.Concurrent2 import Control.Monad3 import System.IO4

5 main = do6 hSetBuffering stdout NoBuffering7 forkIO (replicateM_ 100000 (putChar 'A'))8 replicateM_ 100000 (putChar 'B')

11

Criando uma thread

A segunda linha cria uma thread que imprimirá o caractere Adez mil vezes.

1 import Control.Concurrent2 import Control.Monad3 import System.IO4

5 main = do6 hSetBuffering stdout NoBuffering7 forkIO (replicateM_ 100000 (putChar 'A'))8 replicateM_ 100000 (putChar 'B')

12

Criando uma thread

A terceira linha imprime o caractere B dez mil vezes.

1 import Control.Concurrent2 import Control.Monad3 import System.IO4

5 main = do6 hSetBuffering stdout NoBuffering7 forkIO (replicateM_ 100000 (putChar 'A'))8 replicateM_ 100000 (putChar 'B')

13

Criando uma thread

A execução do programa resultará em caracteres A e Bintercalados:

AAAAAAAAABABABABABABABABABABABABAABABABABAB…

14

Exemplo 1: Lembretes

Vamos criar uma função multithread que aguarda o usuárioentrar com um tempo em segundos e cria uma thread paraimprimir uma mensagem após esse número de segundostenha passado:

1 import Control.Concurrent2 import Control.Monad3

4 main = forever $ do5 s <- getLine6 forkIO $ setReminder s

15

Exemplo 1: Lembretes

A função forever está definida na bibliotecaControl.Monad e simplesmente repete a ação eternamente.A função setReminder pode ser definida como:

1 setReminder :: String -> IO ()2 setReminder s = do3 let t = read s :: Int4 putStrLn ("Ok, adicionado para " ++ show t ++ "

segs.")↪→

5 threadDelay (10^6 * t)6 putStrLn (show t ++ " segundos se passaram! BING!"

A função threadDelay suspende a execução da thread por nmicrosegundos.

16

Exemplo 1: Lembretes

Exemplo de execução:

1 $ ./lembrete2 23 Ok, adicionado para 2 segs.4 45 Ok, adicionado para 4 segs.6 2 segundos se passaram! BING!7 4 segundos se passaram! BING!

17

Exercício 01 (0.5 pto)

Altere o programa anterior para terminar o programa assimque o usuário digitar quit.

DICA: troque o uso de forever por uma função definida porvocê denominada repita.

18

Resposta

1 main = repita2 where repita = do3 s <- getLine4 if s == "quit"5 then return ()6 else do forkIO $ setReminder s7 repita

19

Comunicação entre threads: MVar

Para que as threads possam se comunicar entre si é necessárioa existência de um espaço de memória compartilhada.

No Haskell isso é implementado através do tipo MVar:

1 data MVar a2

3 newEmptyMVar :: IO (MVar a)4 newMVar :: a -> IO (MVar a)5 takeMVar :: MVar a -> IO a6 putMVar :: MVar a -> a -> IO ()

20

MVar

O tipo MVar é um container para qualquer tipo de dado. Vocêpode armazenar um valor Integer, uma String, uma listade Bool, etc.A função newEmptyMVar cria um MVar inicialmente vazio. Afunção newMVar recebe um argumento x e retorna um MVarcontendo x.As funções takeMVar e putMVar, inserem e removem umconteúdo em um MVar.

21

MVar

Notem que MVar armazena apenas um único valor em suaestrutura:

■ Se uma thread chamar takeMVar e ela estiver vazia,ficará bloqueada em espera até que algum conteúdo sejainserido.

■ Se uma thread chamar putMVar e ela estiver cheia, ficarábloqueada em espera até que alguema thread utilizetakeMVar.

22

MVar

Considere o seguinte código exemplo:

1 main = do2 m <- newEmptyMVar3 forkIO $ do putMVar m 'x'; putMVar m 'y'4 r1 <- takeMVar m5 print r16 r2 <- takeMVar m7 print r2

23

MVar

Inicialmente, cria-se uma MVar vazia:

1 main = do2 m <- newEmptyMVar3 forkIO $ do putMVar m 'x'; putMVar m 'y'4 r1 <- takeMVar m5 print r16 r2 <- takeMVar m7 print r2

24

MVar

Em seguida, criamos uma thread que armazena dois caracteresna sequência, ao inserir o primeiro caractere a thread ficabloqueada aguardando espaço ser liberado.

1 main = do2 m <- newEmptyMVar3 forkIO $ do putMVar m 'x'; putMVar m 'y'4 r1 <- takeMVar m5 print r16 r2 <- takeMVar m7 print r2

25

MVar

Nesse momento o thread principal recupera o valor de MVar earmazena em r1, liberando espaço para a thread armazenar osegundo caractere.

1 main = do2 m <- newEmptyMVar3 forkIO $ do putMVar m 'x'; putMVar m 'y'4 r1 <- takeMVar m5 print r16 r2 <- takeMVar m7 print r2

26

MVar

Ao final, o segundo caractere é recuperado e MVar se tornavazio.

1 main = do2 m <- newEmptyMVar3 forkIO $ do putMVar m 'x'; putMVar m 'y'4 r1 <- takeMVar m5 print r16 r2 <- takeMVar m7 print r2

27

Mecanismo anti-burrice

E se o programador escrever o seguinte programa:

1 main = do2 m <- newEmptyMVar3 takeMVar m

28

Mecanismo anti-burrice

A execução retornará:

1 $ ./burro2 burro: thread blocked indefinitely in an MVar operation

Se você criar um programa em que a thread fica bloqueadaeternamente, em muitos casos o programa emite uma exceçãoBlockedIndefinitelyOnMVar.

29

Construindo tipos mutáveis comMVar

Tipos mutáveis

Como sabemos, os tipos do Haskell são imutáveis, ou seja, umavez que definimos uma variável ela não pode mudar de valor.

O tipo MVar pode nos ajudar a simular um tipo mutável apartir de um tipo imutável de forma transparente!

30

Tipo Map

A biblioteca Data.Map fornece o tipo mapa associativo:

1 data Map k a2

3 import qualified Data.Map as M

que define um mapa associativo com chave do tipo k e valoresdo tipo a.

31

Tipo Map

Essa biblioteca possui as funções:

1 M.empty :: Map k a2

3 M.insert :: Ord k => k -> a -> Map k a -> Map k a4 M.insert k v m = -- insere valor v na chave k do mapa m,5 -- substituindo caso já exista6

7 M.lookup :: Ord k => k -> Map k a -> Maybe a8 M.lookup k m = -- retorna o valor na chave k do mapa m,9 -- retorna Nothing caso não exista a chave

32

Agenda telefônica

Vamos criar o tipo agenda telefônica:

1 type Nome = String2 type Numero = String3 type Agenda = M.Map Nome Numero

33

Agenda telefônica

Mas uma agenda telefônica não pode ser uma estruturaimutável. Preciso ter a capacidade de inserir novos nomes eatualizar as entradas. Vamos definir uma agenda telefônicamutável como:

1 newtype AgendaMut = AgendaMut (MVar Agenda)

34

Exercício (0.5 pto)

Defina a função novaAgenda que cria uma agenda mutávelvazia.

1 novaAgenda :: IO AgendaMut

35

Resposta

1 novaAgenda :: IO AgendaMut2 novaAgenda = do m <- newMVar M.empty3 return (AgendaMut m)

36

Exercício

Defina agora a função insere que insere um nome e umtelefone:

1 insere :: AgendaMut -> Nome -> Numero -> IO ()2 insere (AgendaMut m) nome numero = ??

37

Resposta

1 insere :: AgendaMut -> Nome -> Numero -> IO ()2 insere (AgendaMut m) nome numero = do3 agenda <- takeMVar m4 putMVar m (M.insert nome numero agenda)

38

Exercício

Defina a função procura que retorna uma entrada da agenda:

1 procura :: AgendaMut -> Nome -> IO (Maybe Numero)2 procura (AgendaMut m) -> Nome = do

39

Resposta

1 procura :: AgendaMut -> Nome -> IO (Maybe Numero)2 procura (AgendaMut m) nome = do3 agenda <- takeMVar m4 putMVar m agenda -- guarda de volta5 return (M.lookup nome agenda)

40

Agenda Mutável

Dessa forma, podemos trabalhar com a agenda da seguinteforma:

1 nomes = [("Joao", "111-222"), ("Maria", "222-111"),2 ("Marcos", "333-222")]3

4 main = do5 s <- novaAgenda6 mapM_ (uncurry (insere s)) nomes7 print =<< procura s "Marcos"8 print =<< procura s "Ana"

41

Agenda Mutável

O operador =<< é igual ao operador >>= mas invertido.Converta a expressão:

1 print =<< procura s "Marcos"

para a notação do

42

Resposta

1 do x <- procura s "Marcos"2 print x

43

Agenda Mutável

Se essas funções forem utilizadas em um programa que criamúltiplas threads podemos observar algumas vantagens:

■ Durante a operação de busca não é necessário criar umlock no estado da agenda durante a operação.

■ Graças a avaliação preguiçosa, também não se faznecessário um lock por muito tempo no momento dainserção.

44

Operações Assíncronas

Operações Assíncronas

Imagine a situação em que desejamos capturar o conteúdo deuma lista de páginas da Web. Queremos fazer isso de formaconcorrente e, após o encerramento de todas as threads,quero aplicar alguma função nos resultados.

45

Operações Assíncronas (1/3)

Nosso código seria algo como:

1 import Control.Concurrent2 import Data.ByteString as B3 import GetURL4

5 main = do6 m1 <- newEmptyMVar7 m2 <- newEmptyMVar

46

Operações Assíncronas (2/3)

1 forkIO $ do2 r <- getURL "http://www.wikipedia.org/wiki/Shovel"3 putMVar m1 r4

5 forkIO $ do6 r <- getURL "http://www.wikipedia.org/wiki/Spade"7 putMVar m2 r

47

Operações Assíncronas (3/3)

1 r1 <- takeMVar m12 r2 <- takeMVar m23 print (B.length r1, B.length r2)

48

Operações Assíncronas

A ideia é que as duas threads façam o download do conteúdode cada URL em background assincronamente.

Uma operação assíncrona é uma operação que é feita embackground enquanto eu posso fazer outras operações quenão dependam dela, e permita que eu aguarde o final pararealizar alguma operação sobre os resultaos.

49

Operações Assíncronas

O código está muito repetitivo. E se eu quiser fazer a mesmaoperação para uma lista de URLs? Vamos generalizar:

1 data Async a = Async (MVar a)

50

Operações Assíncronas

1 async :: IO a -> IO (Async a)2 async action = do3 var <- newEmptyMVar4 forkIO (do r <- action; putMVar var r)5 return (Async var)

51

Operações Assíncronas

1 wait :: Async a -> IO a2 wait (Async var) = readMVar var

Essas funções estão definidas na bibliotecaControl.Concurrent.Async

52

Operações Assíncronas

A função async cria uma thread a ser executadaassíncronamente com outras.

A função wait aguarda o final da execução de uma threadassíncrona.

53

Operações Assíncronas

Dessa forma nosso código pode ser reescrito como:

1 import Control.Concurrent2 import Data.ByteString as B3 import GetURL4

5 url1 = "http://www.wikipedia.org/wiki/Shovel"6 url2 = "http://www.wikipedia.org/wiki/Spade"7

8 main = do9 a1 <- async (getURL url1)

10 a2 <- async (getURL url2)11 r1 <- wait a112 r2 <- wait a213 print (B.length r1, B.length r2)

54

Operações Assíncronas

Assim, podemos capturar uma lista de sites da seguinte forma:

1 main = do2 as <- mapM (async . getURL) sites3 rs <- mapM wait as4 mapM_ (r -> print $ B.length r) rs

55

Tratamento de Erro

E se uma das URLs não existir ou retornar algum erro?Devemos jogar fora todos os resultados e exibir umamensagem de erro?

A biblioteca async define a função:

1 waitCatch :: Async a -> IO (Either SomeException a)

Que retorna ou um erro, que pode ser tratado, ou o valoresperado. Para tratar o erro devemos importar também importControl.Exception

56

Either

O tipo Either é definido como:

1 data Either a b = Left a | Right b

e assim como o Maybe é utilizado para tratamento de erro. Elediz: ”ou vou retornar algo do tipo a ou do tipo b”, sendo o tipoa geralmente tratado como o erro.

57

Tratamento de Erro

Então podemos definir:

1 printLen :: (Either SomeException B.ByteString) -> IO ()2 printLen (Left e) = print "URL not found"3 printLen (Right b) = print $ B.length b

58

Tratamento de Erro

E então:

1 main = do2 as <- mapM (async . getURL) sites3 rs <- mapM waitCatch as4 mapM_ printLen rs

59

Leituras complementares

Com isso finalizamos o assunto de Programação Concorrentenessa disciplina, embora não tenhamos esgotado todos osconceitos.

Para quem quiser avançar no assunto, a leitura do livro doSimon Marlow é obrigatória!

60

top related