Mecanismos de Tratamento de Exce¸c˜oes em Go, Java e
Haskell
Karla Polyana Silva Falc˜ao
kpsf@cin.ufpe.br
Centro de Inform...
lan¸cada, digamos que ele n˜ao captura coisa alguma.
Representa¸c˜ao de Exce¸c˜oes. Uma exce¸c˜ao ´e lan¸cada atrav´es da ...
Exemplo 2.1 Neste exemplo temos uma exce¸c˜ao sendo lan¸cada em uma fun¸c˜ao b(). Sendo propagada para um n´ıvel acima, a ...
fmt.Println("...Depois?")
}
func c(){
defer func() {
if x := recover(); x != nil {
fmt.Println(x, "entrou em p^anico")
}
f...
func b() {
msg := "b()"
k = &S{msg}
panic(k)
}
func main(){
a()
fmt.Println("Retornou normalmente de a!")
}
Sa´ıda:
Antes....
Saida : 3210
Exemplo 2.6 Ser˜ao colocadas na pilha quatro chamadas ao fmt.Print(i), e na ordem LIFO as sa´ıdas ser˜ao exec...
Uma goroutine ´e uma fun¸c˜ao executando em paralelo com outras goroutines no mesmo espa¸co de mem´oria,
ao mesmo tempo. P...
func main() {
ch1:= make(chan int)
go filtraPares(ch1)
for i:=1;i<=10;i++{ print(<-ch1, " ") }
}
Sa´ıda:
2 4 6 8 10 12 14 ...
package main
import ("time";"fmt")
func b(msg string) {
if x := recover(); x!=nil{
fmt.Println(msg, x)
}else fmt.Println(m...
Exemplo 2.12 (a) Uma chamada a panic() ´e feita ap´os duas goroutines iniciarem, ent˜ao elas morrem e o defer ´e executado...
sinalizador, uma abordagem dinˆamica ´e empregada, determinando assim, que tratador deva ser usado em tempo
de execu¸c˜ao ...
i - -;
throw new Exception();
}finally{ continue; }
}
}
Exemplo 3.4 Neste outro exemplo, o finally encoberta a exce¸c˜ao l...
public int get1(){ return this.i1; }
}
public class FibSinc {
public static void main(String[] args) {
Fib t1 = new Fib(2,...
public static void main(String args[]){
final innerDeadLocl inner = new innerDeadLocl();
lockFaca = new Object();
lockGarf...
objetos completos, n˜ao h´a diferen¸ca entre exce¸c˜oes internas e externas, e a continua¸c˜ao de fluxo de controle segue
o...
Study of Exception Handling Mechanisms for Building Dependable Object-Oriented Software, Journal of Systems
and Software, ...
Próximos SlideShares
Carregando em…5
×

relatorio4

47 visualizações

Publicada em

0 comentários
0 gostaram
Estatísticas
Notas
  • Seja o primeiro a comentar

  • Seja a primeira pessoa a gostar disto

Sem downloads
Visualizações
Visualizações totais
47
No SlideShare
0
A partir de incorporações
0
Número de incorporações
8
Ações
Compartilhamentos
0
Downloads
0
Comentários
0
Gostaram
0
Incorporações 0
Nenhuma incorporação

Nenhuma nota no slide

relatorio4

  1. 1. Mecanismos de Tratamento de Exce¸c˜oes em Go, Java e Haskell Karla Polyana Silva Falc˜ao kpsf@cin.ufpe.br Centro de Inform´atica (CIn) Universidade Federal de Pernambuco (UFPE) 1 Introdu¸c˜ao Uma exce¸c˜ao ´e uma condi¸c˜ao anormal (ou excepcional) ocorrida durante a execu¸c˜ao de um programa, causada por diversos fatores, tais como erros da m´aquina, erros de tipo ou simplesmente uma condi¸c˜ao indesej´avel para o prop´osito do programa. O mecanismo de tratamento de exce¸c˜oes de uma linguagem de programa¸c˜ao ´e respons´avel pela mudan¸ca do fluxo de controle normal de um programa, para um fluxo de controle excepcional, quando uma exce¸c˜ao ´e gerada durante sua execu¸c˜ao. Mecanismos de tratamento de exce¸c˜oes foram desenvolvidos para permitir que desenvolvedores de linguagens de programa¸c˜ao e software, definam e sinalizem as exce¸c˜oes, para indicar que um erro ocorreu durante a execu¸c˜ao de um programa. E assim estruturem as atividades excepcionais de componentes de software, por meio de tratadores. Este trabalho analisa as abordagens do tratamento de exce¸c˜oes em algumas linguagens de programa¸c˜ao. Com o intuito de obter uma vis˜ao ecl´etica, foram estudadas as abordagens mais tradicionais como as de Java [1] (linguagem puramente orientada a objetos com suporte para programa¸c˜ao concorrente), Haskell [2] (linguagem puramente funcional, com caracter´ıstica dos Paradigmas orientada a objetos e concorrente) e a recente abordagem de Go [3] (proposta em agosto de 2010, n˜ao t˜ao distante da cria¸c˜ao desta, desenvolvida em novembro de 2009, que une os paradigmas concorrente, funcional e orientado a objetos). Dando ˆenfase aos v´arios problemas inerentes `a constru¸c˜ao de aplica¸c˜oes paralelas confi´aveis, levando em considera¸c˜ao tamb´em as particularidades de cada linguagem aqui estudada. O que se espera deste trabalho ´e a especifica¸c˜ao informal e aprofundada dos mecanismos de tratamento de exce¸c˜oes para estas linguagens. Principalmente Go, levando em conta o atual mecanismo proposto, para futuramente acrescentar ou at´e reconstruir este. Para este estudo foram utilizados como base, recomenda¸c˜oes para o projeto de um bom mecanismo de tratamento de exce¸c˜oes, do ponto de vista de Engenharia de Software [4]. O trabalho esta organizado em trˆes sess˜oes principais, que especificam os mecanismos de tratamento de exce¸c˜oes nas linguagens Go, Java e Haskell respectivamente. Estando em ordem de importˆancia para a analise. Cada sess˜ao cont´em at´e dez t´opicos, que s˜ao os elementos do mecanismo de tratamento de exce¸c˜oes da linguagem equivalente. Tendo uma maior ˆenfase no tratamento de exce¸c˜oes concorrente, contando at´e com uma breve especifica¸c˜ao da programa¸c˜ao concorrente na linguagem dada, como ´e o caso de Go. 2 Mecanismos de exce¸c˜oes em Go Em Go, lan¸car uma exce¸c˜ao ´e uma sinaliza¸c˜ao, de que o programa em execu¸c˜ao deve finalizar, e apenas blocos salvos considerados “adiados” devam executar normalmente. Nestes poderam haver meios de recuperar e tratar uma exce¸c˜ao qualquer que foi lan¸cada anteriormente, pois uma exce¸c˜ao sobrescreve a outra. Portanto, o tratador n˜ao se importa qual exce¸c˜ao deva ser capturada, ele verifica a ´ultima, at´e a sua execu¸c˜ao. Caso nenhuma seja
  2. 2. lan¸cada, digamos que ele n˜ao captura coisa alguma. Representa¸c˜ao de Exce¸c˜oes. Uma exce¸c˜ao ´e lan¸cada atrav´es da fun¸c˜ao pr´e definida panic(i), que recebe como parˆametro uma interface{}, um tipo que carrega dados, geralmente informa¸c˜ao sobre o tipo da exce¸c˜ao, pode ser uma mensagem de erro (string), um inteiro, etc. An´alogo ao Object de Java, ou o void* de C++. Logo as exce¸c˜oes s˜ao representadas como objetos completos. Assinatura de Exce¸c˜oes Externas e Separa¸c˜ao entre Exce¸c˜oes Internas e Externas. Em Go n˜ao h´a suporte para indicar, nas assinaturas das fun¸c˜oes, exce¸c˜oes que podem ser lan¸cadas externamente, portanto n˜ao h´a qualquer separa¸c˜ao entre exce¸c˜oes internas e externas. Escopo dos Tratadores. Em Go os tratadores est˜ao dentro de fun¸c˜oes, assim todo o escopo de um programa em execu¸c˜ao ou goroutine, ´e uma ´area protegida, em que uma exce¸c˜ao poder´a ser capturada. O tratamento ´e feito atrav´es da verifica¸c˜ao do retorno da fun¸c˜ao recover() ( func recover() interface ), este retorna um tipo interface{}. Quando uma exce¸c˜ao ´e lan¸cada, ou seja, uma chamada a panic(i) ´e feita, a execu¸c˜ao do programa p´ara, da´ı apenas blocos (goroutines) adiados ser˜ao executados, em ordem LIFO. Se for alcan¸cado o topo da pilha, o programa morre. No entanto uma chamada a recover() p´ara a execu¸c˜ao da pilha e retorna o argumento passado para o panic(i), recuperando assim, a execu¸c˜ao normal do programa ao finalizar a pilha. Sendo assim recover() retornar´a nulo(nil), se nenhuma exce¸c˜ao for lan¸cada. Esta verifica¸c˜ao, ´e crucial na hora de tratar uma exce¸c˜ao. Por isso recover() ser´a ´util apenas em blocos adiados, j´a que se n˜ao houver atividade excepcional na fun¸c˜ao, uma chamada a recover() retornar´a nil, sem qualquer outro efeito. Se uma exce¸c˜ao for lan¸cada no n´ıvel da pilha de goroutines, o mesmo acontece. package main import "fmt" type I interface{ toString() string } type S struct{ msg string } func (r *S) toString() string{ return r.msg } func a() { defer func() { if x := recover(); x != nil { fmt.Println(x.(I).toString(), "entrou em p^anico") } fmt.Println("Fun¸c~ao tratadora retornar´a normalmente!") }() fmt.Println("Antes...") b() fmt.Println("...Depois?") // nunca ser´a executado } func b() { var k I msg := "b()" k = &S{msg} panic(k) } func main(){ a() fmt.Println("Retornou normalmente de a!") } Saida: Antes... b() entrou em p^anico Fun¸c~ao tratadora retornar´a normalmente! Retornou normalmente de a!
  3. 3. Exemplo 2.1 Neste exemplo temos uma exce¸c˜ao sendo lan¸cada em uma fun¸c˜ao b(). Sendo propagada para um n´ıvel acima, a fun¸c˜ao a(), e nesta h´a uma fun¸c˜ao adiada que trata a exce¸c˜ao. Logo a partir da´ı o programa recupera o fluxo normal de execu¸c˜ao. Associa¸c˜ao de Tratadores. A associa¸c˜ao de tratadores ´e totalmente dinˆamica. Portanto a fun¸c˜ao tratadora s´o ´e conhecida em tempo de execu¸c˜ao. Isso deve-se ao fato de que em Go n˜ao d´a para saber o tipo de erro capturado apenas olhando a chamada a recover(). ´E necess´ario olhar a implementa¸c˜ao da fun¸c˜ao de recupera¸c˜ao. E tamb´em devido a inferˆencia de tipos de Go, j´a que qualquer tipo ´e considerado uma interface{}. E por isso o efeito torna similar ao de Java. package main import "fmt" type I interface{ toString() string } type S struct{ msg string } func (r *S) toString() string{ return r.msg } func a() { defer func() { if x := recover(); x != nil { fmt.Println(x.(I).toString(), "entrou em p^anico") } fmt.Println("Fun¸c~ao tratadora retornar´a normalmente em a!") }() b() } func c() { defer func() { if x := recover(); x != nil { fmt.Println(x.(I).toString(), "entrou em p^anico") } fmt.Println("Fun¸c~ao tratadora retornar´a normalmente em c!") }() b() } func b() { var k I msg := "b()" k = &S{msg} panic(k) } func main(){ var e int fmt.Scanf("%d", &e) if e > 0 { c() } else { a() } fmt.Println("Retornou normalmente de c ou a!") } Exemplo 2.2 Neste exemplo uma simples entrada de dados, em tempo de execu¸c˜ao decide qual fun¸c˜ao vai tratar um exce¸c˜ao lan¸cada. Propaga¸c˜ao de exce¸c˜oes. Analogamente com Java, a propaga¸c˜ao da exce¸c˜ao ´e multin´ıvel. Se uma fun¸c˜ao esta em “pˆanico” ou estado excepcional, e dentre todas as fun¸c˜oes adiadas n˜ao houver uma chamada a recover() para se recuperar, esta retornar´a para seu chamador imediato e se comportar´a como se fosse um panic(). Assim sucessivamente, a exce¸c˜ao ser´a propagada dentre os componentes do programa, n´ıvel a n´ıvel, at´e que uma fun¸c˜ao tratadora seja encontrada. package main import "fmt" func a() { b()
  4. 4. fmt.Println("...Depois?") } func c(){ defer func() { if x := recover(); x != nil { fmt.Println(x, "entrou em p^anico") } fmt.Println("Fun¸c~ao tratadora retornar´a normalmente em c!") }() fmt.Println("Antes... ") a() } func b() { panic("b()") } func main(){ c() fmt.Println("Retornou normalmente de c ") } Saida : Antes... b() entrou em p^anico Fun¸c~ao tratadora retornar´a normalmente em c! Retornou normalmente de c! Exemplo 2.3 Neste exemplo a main() chama a c() que executa normalmente, colocando uma fun¸c˜ao adiada na pilha, chamando a a(), que chama a b(), onde uma exce¸c˜ao ´e lan¸cada. A exce¸c˜ao ´e propagada, sua execu¸c˜ao morre, pois n˜ao h´a uma fun¸c˜ao tratadora adiada no memso n´ıvel, o mesmo ocorre com a a(), e por fim em c() a exce¸c˜ao ´e tratada em uma fun¸c˜ao adiada, sinalizando uma mensagem que foi passada como argumento no panic(i), que lan¸cou a exce¸c˜ao. Logo, o fluxo de controle continua normalmente a partir do seu n´ıvel. ´E poss´ıvel tamb´em aninhar tratadores dentro de uma mesma fun¸c˜ao. Como no exemplo abaixo: package main import "fmt" type I interface{ toString() string } type S struct{ msg string } var k I func (r *S) toString() string{ return r.msg } func a() { defer func() { if x := recover(); x != nil { fmt.Println(x.(I).toString(), "entrou em p^anico") } fmt.Println("Segunda fun¸c~ao tratadora retornar´a normalmente!") }() defer func() { if x := recover(); x != nil { fmt.Println(x.(I).toString(), "entrou em p^anico") } fmt.Println("Primeira fun¸c~ao tratadora retornar´a em p^anico!") msg := "defer func()" k = &S{msg} panic(k) }() fmt.Println("Antes...") b() fmt.Println("...?") }
  5. 5. func b() { msg := "b()" k = &S{msg} panic(k) } func main(){ a() fmt.Println("Retornou normalmente de a!") } Sa´ıda: Antes... b() entrou em p^anico Primeira fun¸c~ao tratadora retornar´a em p^anico! defer func() entrou em p^anico Segunda fun¸c~ao tratadora retornar´a normalmente! Retornou normalmente de a! Exemplo 2.4 Neste exemplo h´a duas chamadas de fun¸c˜oes via defer na fun¸cao a(), ambas chamando recover(). A segunda (primeira a ser executada), trata a exce¸c˜ao lan¸cada atrav´es de b(), por´em no final do seu bloco h´a outra chamada a panic(). A primeira (segunda a ser executada) captura e trata esta exce¸c˜ao normalmente, devolvendo ao programa seu fluxo normal de execu¸c˜ao. Continua¸c˜ao do fluxo de Controle. A continua¸c˜ao do fluxo de controle, novamente ´e an´aloga a Java. O modelo adotado ´e o de t´ermino com semˆantica de retorno. Isto ´e facilmente verificado com os exemplos anteriores. A¸c˜oes de Limpeza. A¸c˜oes de limpeza com constru¸c˜oes espec´ıficas em Go s˜ao muito poderosas. A declara¸c˜ao defer empurra uma chamada de fun¸c˜ao em uma lista de chamadas, que ´e salva e executada ap´os o retorno da fun¸c˜ao que a envolve. Al´em de fun¸c˜oes para tratamento exce¸c˜oes, defer ´e comumente usado para simplificar blocos que executam v´arias a¸c˜oes de limpeza, tais como, fechamento de arquivos, libera¸c˜ao de um mutex, etc. O defer ´e bastante parecido com o bloco finally de Java, por´em, bem mais poderoso. O comportamento de declara¸c˜oes defer ´e simples e previs´ıvel. Existem trˆes regras simples: 1. Os argumentos de uma fun¸c˜ao adiada s˜ao avaliados quando a instru¸c˜ao defer ´e avaliada; package main func a() { i := 0 defer print(i) i++ return } func main() { a() } Saida : 0 Exemplo 2.5 A express˜ao “i” ´e avaliada quando a chamada fmt.Println(i) ´e adiada. 2. chamadas de fun¸c˜oes adiadas s˜ao executados em ordem Last In First Out, ap´os o retorno da fun¸c˜ao que as envolve; package main func b() { for i := 0; i < 4; i++ { defer print(i) } } func main(){ b() }
  6. 6. Saida : 3210 Exemplo 2.6 Ser˜ao colocadas na pilha quatro chamadas ao fmt.Print(i), e na ordem LIFO as sa´ıdas ser˜ao executadas de tr´as para frente. 3. os valores de retorno da fun¸c˜ao podem ser lidos e atribuidos no escopo de fun¸c˜oes adiadas. package main func c() (i int) { defer func () { i++ }() return 1 } func main() { print(c()) } Sa´ıda: 2 Exemplo 2.7 A chamada adiada da fun¸c˜ao incrementa o valor de retorno i, ap´os o retorno da fun¸c˜ao que a envolve. Isso ´e conveniente para modificar o valor de retorno de erro de uma fun¸c˜ao como por exemplo, o recover(). Verifica¸c˜ao de Confiabilidade. A verifica¸c˜ao de confiabilidade de exce¸c˜oes ´e dinˆamica, ou seja, ´e feita pelo sistema em tempo de execu¸c˜ao. Programa¸cao Concorrente/Tratamento de exce¸c˜ao concorrente. A abordagem concorrente de Go ´e de alto n´ıvel, utilizando-se de canais (representados pela palavra-chave chan), que trasmitem mensagens e dados de um determinado tipo, para assim, controlar o acesso aos valores compartilhados. Ou seja, estes valores nunca s˜ao “realmente” compartilhados por tarefas (goroutines) separadas de execu¸c˜ao. Apenas uma goroutine tem acesso ao valor em qualquer instante dado. Condi¸c˜oes de corrida n˜ao ocorrem, assim os programas s˜ao mais claros e corretos. package main import ("fmt";"time") var i int func a(msg string) { i++ fmt.Println(msg, " est´a pronto... ",i) } func b(msg string) { i-- fmt.Println(msg, " est´a pronto... ",i) } func main() { i=1 go a("caf´e") go b("ch´a") time.Sleep(2*1e9) fmt.Println("Adeus...") } Sa´ıda: caf´e est´a pronto... 2 ch´a est´a pronto... 1 Adeus... Exemplo 2.8 Neste exemplo h´a uma vari´avel global i do tipo int definida, e duas goroutines filhas tentam modificar e acessar a vari´avel, “ao mesmo tempo”. Por´em, apesar de estarem executando paralelamente, o acesso a esta ´e sequencial. Logo, n˜ao h´a inconsistˆencia de mem´oria e a sa´ıda ser´a sempre previs´ıvel.
  7. 7. Uma goroutine ´e uma fun¸c˜ao executando em paralelo com outras goroutines no mesmo espa¸co de mem´oria, ao mesmo tempo. Para executar uma nova goroutine, basta prefixar uma chamada de fun¸c˜ao ou m´etodo com a palavra chave go. Quando a chamada termina, a goroutine finaliza, silenciosamente. A abordagem de concorrˆencia em Go se originou do modelo CSP [5] (Communicating Sequential Processes). Um modelo abstrato, descrito em termos de processos que operam independentemente (paralelismo), e interagem (comunicativo - Canais) uns com os outros e com o ambiente. O comportamento dos processos de CSP pode, ainda, ser descrito em termos de sequˆencia de eventos, que s˜ao opera¸c˜oes atˆomicas e instantˆaneas. Neste tamb´em h´a ferramentas que podem verificar propriedades como: ausˆencia de deadlock, ausˆencia de livelock, comportamento determin´ıstico e refinamentos entre processos. Canais combinam comunica¸c˜ao - a permuta¸c˜ao de um valor - com sincroniza¸c˜ao - garantindo que duas tarefas (denominadas aqui como goroutines) est˜ao em um estado conhecido. Em Go, se uma dada goroutine m˜ae ´e finalizada antes das filhas, ent˜ao estas param junto com a m˜ae. Logo, com canais ´e poss´ıvel garantir a termina¸c˜ao das goroutines filhas antes da m˜ae, como no exemplo abaixo. package main import("fmt";"time") func a(msg string, sec int64) { time.Sleep(sec *1e9) }/a() func main() { go a("ch´a", 3) go a("caf´e", 2) fmt.Println("Fim...") }//main() Sa´ıda: Fim... package main import ("fmt";"time") var c = make(chan string) func a(msg string, sec int64) { time.Sleep(sec *1e9) c <-(msg + "est´a pronto") } //a(m, s) func main() { go a("ch´a", 3) go a("caf´e", 2) fmt.Println("Esperando...") fmt.Println("goroutine finalizada:",<-c) fmt.Println("goroutine finalizada:",<-c) fmt.Println("Fim... ") }//main() Sa´ıda: Esperando... (imediatamente) goroutine finalizada: ch´a est´a pronto (2 segundos depois) goroutine finalizada: caf´e est´a pronto (3 segundos depois) Fim... (logo depois) (a) (b) Exemplo 2.9 (a) As goroutines chamadas n˜ao concluem suas execu¸c˜oes, pois a main() ´e finalizada antes. (b) Agora um canal de tipo string ´e alocado (unbuffer). Duas goroutines (filhas) s˜ao inicializadas na main() (goroutine m˜ae), quando estas finalizarem suas execu¸c˜oes, enviam os sinais (dados do tipo string) pelo canal ( envia com c<-, as execu¸c˜oes s˜ao bloqueadas at´e avaliar o recebimento dos dados). Os dados s˜ao recebidos na main() ( recebe com <-c, a execu¸c˜ao p´ara at´e avaliar o envio dos dados). Exceto o tipo dos dados, todas as avalia¸c˜oes s˜ao feitas em tempo de execu¸c˜ao e se uma dessas falharem, um deadlock ´e capturado. Se diversos dados forem enviados por um mesmo canal, sem serem recebidos, estes ficam enfileirados no canal, at´e serem avaliados os recebimentos destes. Exemplo abaixo, foi criado um tipo de filtro. package main func filtraPares (ch chan int) { for i:=1;;i++ { //Loop "infinito" if i%2==0 {ch <-i} } }
  8. 8. func main() { ch1:= make(chan int) go filtraPares(ch1) for i:=1;i<=10;i++{ print(<-ch1, " ") } } Sa´ıda: 2 4 6 8 10 12 14 16 18 20 Exemplo 2.10 Neste exemplo foi criado um canal, e numa goroutine rodando paralelamente com a main(), v´arios dados est˜ao sendo enviados pelo canal. Enquanto isso a main() est´a recebendo os dez primeiros dados efileirados no canal. ´E importante observar que o loop “infinito” tem que estar numa goroutine filha(paralela), se n˜ao a goroutine m˜ae dorme, e temos um deadlock. Logo, assim que a main() finalizar sua execu¸c˜ao a filha morre e o canal com dados enfileirados ´e desalocado naturalmente. Em Go ´e poss´ıvel testar a comunicabilidade entre os canais, desta forma evita-se deadlocks. Ou seja, ´e poss´ıvel seguir com o fluxo normal de execu¸c˜ao de uma goroutine, mesmo se um dos lados (receber/enviar) de um dado canal falhar. package main import "fmt" var c = make(chan int) func a() { var x int //x = <-c //n~ao ser´a recebido fmt.Println(x,"recebido!") //x = 0 } //a() func main() { go a() fmt.Println("Enviando...", 10) c <- 10 fmt.Println("Fim...") }//main() Sa´ıda: Enviando... 10 0 recebido! throw: all goroutines are asleep - deadlock! ... package main import "fmt" var c = make(chan int) func a() { if x, ok := <-c; ok { //n~ao ser´a recebido fmt.Println(x, "recebido!") }else fmt.Println("valor n~ao recebido!") } //a() func main() { go a() fmt.Println("Enviando...", 10) c <- 10 fmt.Println("Fim...") }//main() Sa´ıda: Enviando... 10 0 recebido! valor n~ao enviado! Fim... (a) (b) Exemplo 2.11 (a) Neste exemplo uma das opera¸c˜oe do canal falhou (o valor enviado n˜ao foi recebido), causando um bloqueio no fluxo de execu¸c˜ao do programa. O programa adormeceu, assim ´e definido um deadlock. (b) Agora o fluxo de execu¸c˜ao ocorrer´a normalmente mesmo se uma das verifica¸c˜oes (receber/enviar) do canal falhar. <-c tem tipo (i interface{}, b bool), o segundo parˆametro ´e opcional, b recebe true se o canal valida o envio de x, recebendo-o. E c<- tem tipo (b bool), b recebe true se o canal valida o recebimento dos dados enviados. Tratamento de Exce¸c˜ao Concorrente. Em Go se houver um panic() n˜ao recuperado em uma goroutine todas as outras no mesmo n´ıvel morrem inclusive a m˜ae, e seus n´ıveis superiores at´e chegar na main(). N˜ao h´a propaga¸c˜ao de exce¸c˜ao entre as goroutines. Abaixo temos algumas demonstra¸c˜oes de problemas no tratamento de exce¸c˜oes em programa¸c˜ao concorrente na linguagem GO. Deve se ter em mente que os resultados dependem da arquitetura do computador em que o programa esteja rodando. Os problemas aqui foram testados em arquiteturas mais usuais, com processadores de dois n´ucleos.
  9. 9. package main import ("time";"fmt") func b(msg string) { if x := recover(); x!=nil{ fmt.Println(msg, x) }else fmt.Println(msg, "Sem p^anico") } //b(m) func a(msg string, sec int64) { defer b(msg) time.Sleep(sec *1e9) fmt.Println(msg, "est´a pronto...") } //a(m, s) func main() { fmt.Println("Antes...") defer b("main") go a("ch´a", 2) go a("caf´e", 1) panic("em p^anico!") time.Sleep(3*1e9) fmt.Println("Fim...") } //main() package main import ("time";"fmt") func b(msg string) { if x := recover(); x!=nil{ fmt.Println(msg, x) }else fmt.Println(msg, "Sem p^anico") } //b(m) func a(msg string, sec int64) { defer b(msg) time.Sleep(sec *1e9) fmt.Println(msg, "est´a pronto...") } //a(m, s) func main() { fmt.Println("Antes...") defer b("main") go a("ch´a", 2) go a("caf´e", 1) go panic("em p^anico!") time.Sleep(3*1e9) fmt.Println("Fim...") } //main() package main import ("time";"fmt") func b(msg string) { if x := recover(); x!=nil{ fmt.Println(msg, x) }else fmt.Println(msg, "Sem p^anico") } //b(m) func a(msg string, sec int64) { defer b(msg) time.Sleep(sec *1e9) fmt.Println(msg, "est´a pronto...") } //a(m, s) func c() { go a("ch´a", 2) go a("caf´e", 1) panic("em p^anico!") } //c() func main() { fmt.Println("Antes...") defer b("main") c() time.Sleep(3*1e9) fmt.Println("Fim...") } //main() (a) (b) (c) package main import ("time";"fmt") func b(msg string) { if x := recover(); x!=nil{ fmt.Println(msg, x) }else fmt.Println(msg, "Sem p^anico") } //b(m) func a(msg string, sec int64) { defer b(msg) time.Sleep(sec *1e9) fmt.Println(msg, "est´a pronto...") } //a(m, s) func c() { go a("ch´a", 2) go a("caf´e", 1) go panic("em p^anico!") } //c() func main() { fmt.Println("Antes...") defer b("main") c() time.Sleep(3*1e9) fmt.Println("Fim...") } //main() package main import ("time";"fmt") func b(msg string) { time.Sleep(4*1e9) if x := recover(); x!=nil{ fmt.Println(msg, x) }else fmt.Println(msg, "Sem p^anico") } //b(m) func a(msg string, sec int64) { defer b(msg) time.Sleep(sec *1e9) fmt.Println(msg, "est´a pronto...") } //a(m, s) func c() { defer b("c()") go a("ch´a", 2) go a("caf´e", 1) go panic("em p^anico!") } //c() func main() { fmt.Println("Antes...") defer b("main") c() time.Sleep(3*1e9) fmt.Println("Fim...") } //main() package main import ("time";"fmt") func b(msg string) { time.Sleep(4*1e9) if x := recover(); x!=nil{ fmt.Println(msg, x) }else fmt.Println(msg, "Sem p^anico") } //b(m) func a(msg string, sec int64) { defer b(msg) time.Sleep(sec *1e9) fmt.Println(msg, "est´a pronto...") } //a(m, s) func c() { defer b("c()") go a("ch´a", 2) go a("caf´e", 1) go panic("em p^anico!") } //c() func main() { fmt.Println("Antes...") defer b("main") go c() time.Sleep(3*1e9) fmt.Println("Fim...") } //main() (d) (e) (f)
  10. 10. Exemplo 2.12 (a) Uma chamada a panic() ´e feita ap´os duas goroutines iniciarem, ent˜ao elas morrem e o defer ´e executado, a exce¸c˜ao ´e tratada e o programa termina normalmente. (b) Trˆes goroutines rodam concorrentemente na fun¸c˜ao main() ( ´ultima chama panic() ). Mesmo que a fun¸c˜ao adiada seja avaliada antes da goroutine chamando o panic(), ela n˜ao ser´a executada. A execu¸c˜ao de todo o programa termina, excepcionalmente, quando o panic() ´e executado, n˜ao importa em que posi¸c˜ao esteja o defer na main(). (c) H´a uma chamada a fun¸c˜ao c(), que executa duas goroutines e uma chamada a panic(), a fun¸c˜ao morre assim como as goroutines filhas. A exce¸c˜ao ´e propagada para a fun¸c˜ao main(), o defer ´e executado e o programa termina normalmente. (d) Agora o panic() ´e executado em uma goroutine, que est´a em outra fun¸c˜ao chamada. A execu¸c˜ao do programa e das goroutines ´e interrompida e finalizada excepcionalmente assim que o panic() ´e executado. (e) A goroutine que executa o panic() est´a em outra fun¸c˜ao chamada, que tamb´em tem uma fun¸c˜ao tratadora. A execu¸c˜ao do programa e das goroutines ´e interrompida e finalizada excepcionalmente assim que o panic() ´e executado. (f) Neste exemplo a goroutine que executa o panic() est´a em um n´ıvel a mais (goroutine), que tamb´em tem uma fun¸c˜ao tratadora. Por´em o resultado ´e an´alogo ao anterior, n˜ao importa o n´ıvel em que o panic() seja lan¸cado. 3 Mecanismos de exce¸c˜oes em Java Representa¸c˜ao de Exce¸c˜oes. Exce¸c˜oes em Java s˜ao representadas como objetos de dados, ou seja, s˜ao classes bem definidas hierarquicamente. Java adota uma solu¸c˜ao h´ıbrida para exce¸c˜oes, n˜ao havendo distin¸c˜ao entre exce¸c˜oes internas e externas. Todas as exce¸c˜oes devem ser herdadas (direta ou indiretamente) da classe Throwable. Esta classe por sua vez tem duas subclasses predefinidas, Error e Exception. Todas as exce¸c˜oes que herdam de Exception s˜ao verificadas, ou “trat´aveis” tais como: coer¸c˜ao inv´alida, divis˜ao por zero, etc. E as exce¸c˜oes que herdam de Error s˜ao n˜ao-verificadas, ou “n˜ao-trat´aveis” tais como: erros causados pela M´aquina Virtual Java, falta de mem´oria, etc. Assinatura de Exce¸c˜oes Externas e Separa¸c˜ao entre Exce¸c˜oes Internas e Externas. Como em Java n˜ao h´a qualquer distin¸c˜ao entre exce¸c˜oes internas e externas, a declara¸c˜ao de exce¸c˜oes nas assinaturas de m´etodos ´e obrigat´oria a menos que sejam exce¸c˜oes n˜ao-verificadas. Escopo dos Tratadores. Quando uma exce¸c˜ao ´e lan¸cada, uma instˆancia da classe Exception (Um objeto Ex- ception) ´e criada e transmitida como argumento para o tratador correspondente. O lan¸camento de exce¸c˜oes ´e feito usando a palavra reservada throw. As regi˜oes protegidas s˜ao blocos em que exce¸c˜oes podem lan¸cadas e cap- turadas: try {// C´odigo que pode gerar uma exce¸c~ao}. Se for detectado um lan¸camento de uma determinada exce¸c˜ao, um tratador associado a esta regi˜ao e exce¸c˜ao ser´a ativado, atrav´es do bloco: catch (MinhaExcecao e) {// C´odigo para tratar a exce¸c~ao}. class Exemplo1{ public static void main(String[] a){ int[] vet = new int[3]; try{ for(int c = 0; c < 4; c++) vet[c] = 0; }catch(Exception e){ System.out.println(‘‘Houve um erro!’’); } System.out.println(‘‘Fim do programa.’’); } } Exemplo 3.1 Associa¸c˜ao de Tratadores. Em Java qualquer exce¸c˜ao verificada que pode ser lan¸cada direta ou indiretamente no escopo de um m´etodo deve ser tratada ou especificada, por isso h´a associa¸c˜ao semi-dinˆamica de tratadores, um modelo h´ıbrido que combina as abordagens est´atica e dinˆamica. Ou seja, tratadores locais podem estar vinculados estaticamente ao sinalizador da exce¸c˜ao, por´em se um tratador n˜ao est´a ligado `a devida exce¸c˜ao no contexto do
  11. 11. sinalizador, uma abordagem dinˆamica ´e empregada, determinando assim, que tratador deva ser usado em tempo de execu¸c˜ao para uma determinada ocorrˆencia de exce¸c˜ao. class Exemplo2{ void m1(){ throw new E(); } void m2(){ try{ m1(); }catch(E e){ ... } } void m3(){ try{ m1(); }catch(E e){ ... } } } Exemplo 3.2 Quem tratar´a a exce¸c˜ao lan¸cada em m1? Propaga¸c˜ao de Exce¸c˜oes. Havendo associa¸c˜ao semi-dinˆamica de tratadores (em tempo de execu¸c˜ao), uma deter- minada exce¸c˜ao pode ser propagada entre os componentes de um sistema. Em Java a propaga¸c˜ao ´e autom´atica ou multi-n´ıvel, se nenhum tratador for encontrado, no pr´oprio escopo, para uma exce¸c˜ao lan¸cada, esta ser´a propagada automaticamente para os componentes de n´ıvel mais alto, at´e que um tratador possa ser encontrado. Ou seja, uma exce¸c˜ao pode ser tratada por outros componentes do seu chamador imediato. Continua¸c˜ao do Fluxo de Controle. A continua¸c˜ao de fluxo de controle segue modelo de t´ermino com semˆantica de retorno. Portanto, logo ap´os uma exce¸c˜ao ser lan¸cada, o bloco sinalizador ´e finalizado, direto com a seguinte declara¸c˜ao de ´area protegida, onde a exce¸c˜ao ser´a tratada. Caso nenhum tratador adequado for encontrado, o fluxo continua numa pr´oxima ´area protegida, seja de tratamento ou n˜ao. A¸c˜oes de Limpeza. Java fornece constru¸c˜oes espec´ıficas de a¸c˜oes de limpeza, definidas pelo bloco finally{}. Este ´e sempre executado ap´os a execu¸c˜ao do bloco try anexado, seja uma exce¸c˜ao lan¸cada ou n˜ao. Havendo um return, o bloco finally ser´a sempre executado antes da sa´ıda do m´etodo, se n˜ao houver return no bloco try nem no bloco finally, o fluxo de execu¸c˜ao continua ap´os o bloco finally. Qualquer desvio do fluxo de controle no bloco finally, sobrescrever´a uma exce¸c˜ao que foi lan¸cada acima dele, no bloco try. class Exemplo3{ Exemplo3(){ m1(); } void m1(){ try{ throw new Exception(); }finally{ return; } } } Exemplo 3.3Neste exemplo o return dentro do bloco finally, camuflar´a a exce¸c˜ao lan¸cada no bloco try. class Exemplo4{ Exemplo4(){ m1(); } void m1(){ int i = 100; while ( i > 0) try {
  12. 12. i - -; throw new Exception(); }finally{ continue; } } } Exemplo 3.4 Neste outro exemplo, o finally encoberta a exce¸c˜ao levantada no try, dando um continue no loop, assim na ´ultima avalia¸c˜ao do while n˜ao haver´a exce¸c˜ao para ser propagada. H´a tamb´em a¸c˜oes de limpeza autom´atica, com o Garbage Collector, ou coletor de lixo, que cuida do desaloca- mento de mem´oria autom´aticamente. Verifica¸c˜ao de Confiabilidade. A verifica¸c˜ao de confiabilidade, ou a verifica¸c˜ao de tratamento de exce¸c˜oes, ´e feita parte em tempo de compila¸c˜ao, e parte em tempo de execu¸c˜ao. Tratamento de Exce¸c˜ao Concorrente. Embora Java descreva claramente a semˆantica de tratamento de exce¸c˜oes em programas concorrentes, o tratamento de exce¸c˜oes concorrentes nesta ´e limitado. O mecanismo de exce¸c˜ao de Java ´e integrado com o modelo de Java thread/sincroniza¸c˜ao: todos os bloqueios do objeto receptor s˜ao liberados quando um m´etodo sincronizado de sinais acontece, da exce¸c˜ao para o chamador. Uma exce¸c˜ao ass´ıncrona (sinal) pode ser levantada em um segmento de programa concorrente, invocando o m´etodo stop da classe Thread. Por´em, Java perdeu as caracter´ısticas que permitiam a parada e reinicializa¸c˜ao da execu¸c˜ao de threads perdidas, para evitar que objetos fiquem em um estado inconsistente. O m´etodo stop() foi deprecated. Alguns exemplos em Java, de exce¸c˜oes em programa¸c˜ao concorrente que n˜ao tem tratamento : 1. Interleave e Inconsistencia de Mem´oria - Esse programa calcula, os primeiros 50 termos da sequˆencia de Fibonacci concorrentemente, a primeira e a segunda metade. Por´em como o acesso das threads aos termos(vari´aveis compartilhadas) anteriores ´e simultˆaneo, os resultados se sobrep˜oem, havendo portanto uma inconsistˆencia nos valores da sequencia. public class Fib extends Thread { int b, f, i0, i1 ; public Fib ( int b, int f, int i0, int i1){ this.b = b; this.f = f; this.i0 = i0; this.i1 = i1; this.start(); } public void run(){ int i; int[] a = new int[100]; synchronized (a){ a[b-2] = i0; a[b-1] = i1; for (i = b; i < f; i++ ){ a[i] = a[i-1]+ a[i-2]; System.out.println(i+" - "+a[i]); } this.i0 = a[i-2]; this.i1 = a[i-1]; } } public int get0(){ return this.i0; }
  13. 13. public int get1(){ return this.i1; } } public class FibSinc { public static void main(String[] args) { Fib t1 = new Fib(2, 25, 1, 1); Fib t2 = new Fib(25, 50, t1.get0(), t1.get1()); } } Exemplo 3.5 2. Deadlock - Neste exemplo h´a um restaurante com 1 cozinheiro e 2 clientes representados por trˆes threads, os dois Clientes estao na mesma mesa, e na mesa ha apenas um garfo e uma faca, o cliente so podera comer caso pegue os dois, o garfo e a faca. Parecido com o problema dos filosofos. public class DeadLock { static boolean temFaca = true, temGarfo = true, alguemComendo = false; static Object lockFaca, lockGarfo, temComida; static class innerDeadLocl{ public void cozinhar(){ synchronized(temComida){ temComida.notifyAll(); } } public void verificarSeTemComida() throws InterruptedException{ boolean Comida = false; if(!Comida){ synchronized(temComida){ temComida.wait(); } } } public boolean pegarFaca() throws InterruptedException{ if(!temFaca){ synchronized(lockFaca){ lockFaca.wait(); } } temFaca = false; return true; } public boolean pegarGarfo() throws InterruptedException{ if(!temGarfo){ synchronized(lockGarfo){ lockGarfo.wait(); } } temGarfo = false; return true; } public synchronized void Comer(){ alguemComendo = true; System.out.println(Thread.currentThread().getName()+" que delicia"); } } static class Faminto extends Thread{ boolean faca = false, garfo = false; int time =0; final innerDeadLocl inner = new innerDeadLocl(); Faminto(int time){ this.time = time;} public void run() { try { Thread.currentThread(); Thread.sleep(time); } catch (InterruptedException e2) { e2.printStackTrace(); } try { inner.verificarSeTemComida(); } catch (InterruptedException e1) { e1.printStackTrace(); } try { faca = inner.pegarFaca(); } catch (InterruptedException e) { e.printStackTrace(); } try { garfo = inner.pegarGarfo(); } catch (InterruptedException e) { e.printStackTrace(); } if(faca && garfo){ inner.Comer(); } } }
  14. 14. public static void main(String args[]){ final innerDeadLocl inner = new innerDeadLocl(); lockFaca = new Object(); lockGarfo = new Object(); temComida = new Object(); Faminto faminto1 = new Faminto(500); Faminto faminto2 = new Faminto(400); Thread Cozinheiro = new Thread(new Runnable() { public void run() { inner.cozinhar(); //Ocorrencia do deadlock if(!alguemComendo){ for(int i = 0; i < 3; i++){System.out.println("Olha a comida pessoal"); } System.out.println("Ninguem esta comendo!"); } } }); faminto1.start(); faminto2.start(); Cozinheiro.start(); } } Exemplo 3.6 4 Mecanismos de exce¸c˜oes em Haskell Representa¸c˜ao de Exce¸c˜oes. Os mecanismos de Haskell para capturar e tratar exce¸c˜oes n˜ao s˜ao feitos atrav´es de uma sintaxe especial embutida nos c´odigos como em Java, mas sim atrav´es de fun¸c˜oes. Em Haskell, existem dois tipos principais de mecanismos de tratamento de erros: Tratamento de erro puro e Tratamento de exce¸c˜oes. No tratamento de erro puro, as fun¸c˜oes s˜ao “puras”, ou seja, n˜ao mexem com o tipo de dados IO (entrada e sa´ıda) da classe Monad, neste caso, s˜ao usados os tipos de dados Maybe ( data Maybe a = Nothing | Just a ) e Either ( data Either b = Left b | Right ) tamb´em da classe Monad, para indicar erros, logo as exce¸c˜oes s˜ao represen- tadas atrav´es de s´ımbolos. Assinatura de Exce¸c˜oes Externas - Separa¸c˜ao entre Exce¸c˜oes Internas e Externas - Associa¸c˜ao de Tratadores - Continua¸c˜ao do fluxo de Controle. Caso a fun¸c˜ao use o tipo Maybe, se um determinado erro ´e encontrado, esta retorna Nothing, se n˜ao, ent˜ao retorna Just com os devidos resultados. No caso esta use o tipo Either, se um determinado erro ´e encontrado, esta retorna Left com uma mensagem criada pelo programador, usualmente indicando caracter´ısticas do erro, se n˜ao, retorna Right com os devidos resultados desta fun¸c˜ao. Da´ı ´e poss´ıvel tamb´em criar tipos de dados personalizados para erros, utilizando a classe Error. A vantagem de se utilizar este tratamento ´e que a forma de indicar erros fica muito simples, por´em al´em de ter de se preocupar com a preserva¸c˜ao da pregui¸ca, que ´e uma das grandes caracter´ısticas de Haskell, n˜ao ´e poss´ıvel o tratamento de erros. Devido `a ordem de avalia¸c˜ao indeterminada, exce¸c˜oes podem ser lan¸cadas em qualquer lugar, mas s´o tratadas na mˆonada IO. Haskell tem tamb´em um sistema de tratamento de exce¸c˜oes predefinido usando monadas IO. As fun¸c˜oes que lan¸cam, capturam e tratam exce¸c˜oes explicitamente s˜ao separadas e est˜ao definidas no m´odulo Control.Exception, todas as exce¸c˜oes s˜ao do tipo Exception, tamb´em definido neste. Neste caso as exce¸c˜oes s˜ao representadas como
  15. 15. objetos completos, n˜ao h´a diferen¸ca entre exce¸c˜oes internas e externas, e a continua¸c˜ao de fluxo de controle segue o modelo de t´ermino de execu¸c˜ao do c´odigo, que neste caso se aplica ao encerramento da execu¸c˜ao de uma fun¸c˜ao. Uma fun¸c˜ao b´asica de Haskell para capturar exce¸c˜oes ´e try, de tipo, IO a → IO (EitherException a), que envolve uma a¸c˜ao IO com tratamento de exce¸c˜ao. Se uma exce¸c˜ao foi levantada, ela ir´a retornar um valor Left, com a exce¸c˜ao, caso contr´ario, um valor Right com o resultado original. Para capturar uma exce¸c˜ao lan¸cada pelo c´odigo puro, ´e preciso usar a fun¸c˜ao evaluate (do m´odulo Control.Exception), de tipo : a → IO a, pois ela for¸ca a avalia¸c˜ao da fun¸c˜ao sinalizadora (que levanta a exce¸c˜ao). Tamb´em ´e poss´ıvel lidar com exce¸c˜oes de I/O, e s˜ao representadas pelo tipo de dados Exception. Para isto o Haskell disponibiliza os v´arios tipos de exce¸c˜oes I/O no m´odulo System.IO.Error. Neste h´a fun¸c˜oes como try e catch, para lidar apenas com esses tipos de exce¸c˜oes. Escopo dos Tratadores. Em Haskell s´o ´e poss´ıvel anexar tratadores de m´etodo, estes est˜ao associados a uma fun¸c˜ao, quando uma exce¸c˜ao ´e levantada em uma das declara¸c˜oes da fun¸c˜ao, o tratador de m´etodo em anexo a pre- sente fun¸c˜ao sinalizadora ´e executado, o tratador ´e usado em tempo de execu¸c˜ao para uma determinada ocorrˆencia de exce¸c˜ao. Para tratamento h´a uma fun¸c˜ao chamada handle, de tipo : (Exception → IO a) → IO a → IO a. O primeiro parˆametro ´e uma fun¸c˜ao a ser chamada (fun¸cao tratadora) caso haja uma exce¸c˜ao durante a execu¸c˜ao da segunda. O problema desta fun¸c˜ao ´e que h´a uma ´unica forma de tratar qualquer que seja a exce¸c˜ao, por´em h´a tamb´em uma fun¸c˜ao chamada handleJust, de tipo : (Exception e) ⇒ (e → Maybe b) → (b → IO a) → IO a → IO a. Na qual ´e poss´ıvel definir uma determinada exce¸c˜ao que queira tratar, deixando todos os outros tipos de exce¸c˜oes passarem. Logo ´e poss´ıvel tratar determinadas exce¸c˜oes j´a predefinidas neste m´odulo, deixando o sistema muito mais robusto. Existem duas formas a lan¸car exce¸c˜oes com as fun¸c˜oes em Control.Exception, a mais comum ´e throw , de tipo, Exception → a. Esta fun¸c˜ao pode lan¸car qualquer Exception, e pode tamb´em fazˆe-lo em um contexto puro. H´a tamb´em a fun¸c˜ao throwIO de tipo : Exception → IO a. Que lan¸ca uma exce¸c˜ao na mˆonada IO. E tamb´em a fun¸c˜ao ioError , que ´e definida de forma idˆentica em ambos os m´odulos, Control.Exception e System.IO.Error de tipo : IOError → IO a. Isso ´e usado quando se deseja gerar uma exce¸c˜ao arbitr´aria rela- cionada com I/O. Propaga¸c˜ao de Exce¸c˜oes. N˜ao h´a suporte. A¸c˜oes de Limpeza. N˜ao h´a suporte. Verifica¸c˜ao de Confiabilidade. N˜ao h´a suporte. Tratamento de Exce¸c˜ao Concorrente. Apenas suas velocidades e mem´orias de uso - sem concorrˆencia de dados ou deadlocks! 5 Referˆencias [1] James Gosling, Bill Joy, and Guy Steele - The Java Language Specification, Addison-Wesley, 1996. [2] Bryan O’Sullivan, Don Stewart e John Goerzen - Real World Haskell, O’Reilly, 2008. [3] Google - golang.org , Google, November, 2009. [4] Alessandro F. Garcia, Cec´ılia M. F. Rubira, Alexander Romanovsky e Jie Xu - A Comparative
  16. 16. Study of Exception Handling Mechanisms for Building Dependable Object-Oriented Software, Journal of Systems and Software, Volume 59, November 2001, Pages 197-222. [5] Tony Hoare - Communicating sequential processes, Comm. of the ACM, 21(8):666 - 677, 1978.

×