Você está na página 1de 5

TPC1 - Jantar dos Filósofos

José Eduardo Rodrigues Serpa, Mateus de Carvalho de Freitas


(jose.serpa@edu.pucrs.br) (mateus.carvalho99@edu.pucrs.br)
Pontifícia Universidade Católica do Rio Grande do Sul
Porto Alegre, RS

1. Introdução deixamos rodar apenas uma vez para cada filósofo. O


O Jantar dos filósofos foi proposto por Dijkstra programa, no fluxo principal, cria um vetor com cinco
em 1965 como um problema de sincronização. A partir garfos e outro vetor com cinco filósofos. E então chama
de então todos os algoritmos propostos como soluções o método comer de sua struct. O comportamento é
de sincronização acabaram sendo relacionados ou como o esperado de uma implementação sequencial,
testados contra o clássico problema do Jantar dos cada filósofo realiza a rotina de cada vez. Isso é,
filósofos. primeiro o filósofo um pensa e come, para então o
próximo filósofo começar sua rotina, este processo se
2. Definição repete até o fim do vetor de filósofos. A fim printamos
Temos cinco filósofos que estão sentados em quanto tempo levou, pois, nas próximas duas soluções
uma mesa redonda para jantar. Cada filósofo tem um damos um tempo limite de execução de 10 segundos.
prato de comida à sua frente. Cada prato possui um Não é a melhor comparação, mas servirá para ilustrar
garfo para pegar a comida. E para que um filósofo as vantagens da programação concorrente.
consiga comer, será necessário utilizar dois garfos.
Porém há um garfo entre cada filósofo. Um filósofo só 3.2 Solução com Canais
pode comer se puder pegar os dois garfos adjacentes a Podemos pensar em canais como sendo um
ele. Um garfo pode ser pego por qualquer um de seus túnel de comunicação entre goroutines, onde uma
seguidores adjacentes, mas não por ambos. Neste goroutine consegue enviar informações para outra antes
caso, cada filósofo alterna entre duas tarefas: comer ou mesmo de terminar sua execução. Em Go temos o tipo
pensar. Quando um filósofo fica com fome, ele tenta de dado chan que serve para declarar um canal -
pegar os garfos à sua esquerda e à sua direita; um de exemplo var c chan, seu valor inicial será nil e para
cada vez, independente da ordem. Caso ele consiga iniciá-lo, devemos usar a função make do Go - exemplo
pegar dois garfos, ele come durante um determinado int c = make(chan int). Nesse mesmo cenário, a
tempo e depois recoloca os garfos na mesa. Em goroutine que recebe a informação, ficaria pausada até
seguida ele volta a pensar. as informações chegarem. Em relação à estrutura do
código, começamos com uma struct nomeada diner, tal
Neste artigo o nosso assunto central será a estrutura contém os elementos necessários nesta
programação concorrente. Este paradigma foca-se implementação para o jantar, como o tempo de pensar,
principalmente na interação e execução de multitarefas, o tempo de comer, os canais que serão usados e o
assim sendo, uma das principais preocupações quando número de filósofos. Estrutura construída, passa-se
nos deparamos com um algoritmo deste género são os agora para a “organização” do jantar presente na func
problemas que podem ocorrer na sincronização e na newDiner(...), nesta declara-se uma variável diner e
partilha de recursos. E neste problema podemos inicia-se todos os elementos com os respectivos
considerar alguns casos, como um filósofo que passa valores. Agora as chaves para a solução funcionar
fome (starvation) ou no caso onde cada filósofo pega estão nas funções func philo(...) e func fork(...). Nestas
um garfo e ninguém come (deadlock). simulamos os atos de pensar e de comer, onde cada
garfo é um processo que é conectado por um canal aos
3. Soluções filósofos à sua esquerda e direita. Haverá cinco
O nosso objetivo é ter uma implementação que processos filósofos e cinco processos bifurcados. Os
nos permita simular este jantar sem que haja problemas processos filósofos começam esperando por uma
de deadlock e starvation. A fim de tornar possível uma entrada nos dois canais de bifurcações adjacentes.
comparação no nível de concorrência das soluções, Eventualmente, os processos de bifurcação produzirão
todas foram realizadas na linguagem Go. valores em seus canais, nestes usamos valores
booleanos e apenas os ignoramos na entrada.Uma vez
3.1 Solução sequencial que um processo fork produz um valor, ele é bloqueado
Para ao fim realizar uma comparação de até receber entrada do canal. Isso só ocorrerá quando o
performance se deu a implementação de uma solução filósofo que anteriormente recebia um valor no canal
sequencial. Essa possui dois structs, um para o garfo e retornar um valor após comer. E por fim a construção de
outro para o filósofo. O filósofo possui uma “rotina”, que uma func start(), que ao chamá-la uma vez em nosso
é a de pensar, esperar um segundo (para simular um main(), inicializamos todos os processos atrás do outro
acesso ao recurso), comer, esperar mais um segundo e dentro de um for. Isso funciona no problema porque um
finalizar a rotina, após mais um segundo a rotina pode canal pode ser usado por mais de um par de processos.
se repetir por mais n vezes, mas nesta implementação
3.3 Solução com Semáforos - Mutex solução para este caso - algumas vezes o filósofo morre
Para a solução com semáforos, estrutura de fome.
denominada como Mutex em Go - conta com os
métodos de Lock() e Unlock(), que equivale a um
semáforo binário que permitem que só um processo
entre na seção crítica, que por sua vez possibilita a
implementação dos mecanismos de exclusão mútua.
Em relação ao código, nesta implementação é criado
um vetor com os garfos, seguidos pela criação dos
filósofos. Ambos possuem structs próprios. A possui
uma struct philosofer, armazena o id do filósofo e dois
ponteiros do tipo “fork” que levam aos os garfos
próximos, sendo eles o garfo da direita e esquerda de 4.3 Semáforos - Mutex
cada filósofo. A struct fork implementa somente a A partir do resultado do tempo do sequencial
propriedade sync.Mutex. Então, cada filósofo começa como base para teste. No total, para este caso teste,
sua rotina de comer, chamando concorrentemente a foram feitas 74 operações de comer.
função func eat(). A func eat() funciona da seguinte
forma, ao longo das iterações os filósofos irão alternar
entre comer e pensar concorrentemente e no ato de
comer ela é responsável pelo processo de exclusão
mútua, ou seja, se um filósofo for comer nem um outro
pode pegar os mesmos garfos que ele, então vou proibir
o acesso aos garfos que estão sendo utilizados usando
os métodos de Lock() e Unlock() para esta operação,
após comer libera-se os garfos para o próximo que
queira comer. O programa inicia atribuindo um arranjo
de 5 posições da struct fork, e outro também de 5 5. Considerações Finais
posições da struct philosofer e logo em seguida dispara Chegados à reta final deste relatório
a func eat() para cada filósofo em uma thread diferente, gostaríamos mais uma vez de recordar as vantagens da
com o objetivo de fazer os 5 competirem pelos garfos na programação concorrente sendo elas o aumento do
mesa. Contudo, durante a execução do programa terão desempenho do programa, devido ao aumento da
5 filósofos tentando ter acesso aos garfos adjacentes quantidade de tarefas que podem ser executadas
para comer, fazendo com que todos funcionem em durante um determinado período de tempo. Por isso a
paralelo concorrendo pelo acesso aos garfos de forma se deve atentar a importância do bom uso dos
sincronizada. mecanismos de sincronização.

4. Análise em gráficos

4.1 Sequencial
Tempo de execução total, com 3 iterações para
cada filósofo, resultou em 45 segundos. No total, para
este caso teste, foram feitas 15 operações de comer.

4.2 Canal
A partir do resultado do tempo do sequencial
como base para teste. No total, para este caso teste,
foram feitas 61 operações de comer. Nesta
implementação nota-se um caso de starvation - filósofo
2. Tentamos corrigir, mas não encontramos a correta
6. Capturas de tela

Figura 1: Execução em Sequencial Figura 2: Execução em Canal Figura 3: Execução em Semáforo

7. Códigos-fonte

7.1 Código do Sequencial

package main }

import ( func printformatado(acao string, id int) {


"fmt" fmt.Printf("filosofo #%d %s\n", id+1, acao)
"time" }
)
func main() {
type garfo struct{} start := time.Now()
count := 5 //nfilosofos
type filosofo struct {
id int garfos := make([]*garfo, count)
esqgarfo, dirgarfo *garfo for i := 0; i < count; i++ {
} garfos[i] = new(garfo)
}
func (f filosofo) comer() {
for j := 0; j < 10; j++ { filosofos := make([]*filosofo, count)
printformatado("esta pensando", f.id) for i := 0; i < count; i++ {
time.Sleep(time.Second) filosofos[i] = &filosofo{
id: i, esqgarfo: garfos[i], dirgarfo: garfos[(i+1)%count]}
printformatado("esta comendo", f.id) filosofos[i].comer()
time.Sleep(time.Second) }

//printformatado("terminou de comer", f.id) elapsed := time.Since(start)


//time.Sleep(time.Second) fmt.Printf("Binomial took %s", elapsed)
} }
7.2 Código utilizando Canal

package main left := false


right := false
import ( // check if we can left fork
"fmt" select {
"math/rand" case left = <-d.free[p]:
"sync" break
"time" ) default:
left = false
type diner struct { break
thinkTimeSecs int }
eatTimeSecs int
free []chan bool if !left {
done []chan bool fmt.Printf("Filosofo %d, pensando\n", p)
n int continue }
c *sync.Cond
started chan int //try and get right one
randomize bool select {
} case right = <-d.free[((p + 1) % d.n)]:
break
func newDiner(n int, eatTime int, thinkTime int, randomize bool) *diner { default:
d := new(diner) right = false
d.n = n break
// first create n fork channels and philo channels as }
//communication between forks and philosophers
d.free = make([]chan bool, n) if !right {
d.done = make([]chan bool, n) // then free left as well
d.done[p] <- true
d.c = sync.NewCond(&sync.Mutex{}) fmt.Printf("Filosofo %d, pensando\n", p)
continue
d.thinkTimeSecs = thinkTime }
d.eatTimeSecs = eatTime
d.randomize = randomize if left && right {
// eat and then release
// make a buffered channel fmt.Printf("Filosofo %d, comeu\n", p)
d.started = make(chan int, n) time.Sleep(eat)
// indicate done
for i := 0; i < n; i++ { d.done[p] <- true
d.free[i] = make(chan bool) d.done[((p + 1) % d.n)] <- true
d.done[i] = make(chan bool) }
}
return d }
}
}
func (d *diner) start() {
func (d *diner) fork(f int) {
for i := 0; i < d.n; i++ {
think := d.getTime(d.thinkTimeSecs) for {
eat := d.getTime(d.eatTimeSecs)
go d.fork(i) // indicate that the fork is free
go d.philo(i, think, eat) d.free[f] <- true
}
} //fmt.Printf("Waiting for fork %d to be done\n",f)
//wait for it to be used and then released
func (d *diner) getTime(tSec int) time.Duration { select {
case <-d.done[f]:
//return a random time between 0 to t break
if d.randomize { }
return time.Millisecond * time.Duration(rand.Intn(tSec*1000)) }
}
return time.Millisecond * time.Duration(tSec*1000) }
}
func main() {
func (d *diner) philo(p int, think time.Duration, eat time.Duration) {
for { d := newDiner(5, 2, 1, true)
// think time d.start()
//fmt.Printf("%d,think\n",p)
time.Sleep(think) time.Sleep(45 * time.Second)
fmt.Printf("Filosofo %d,com fome\n", p) fmt.Printf("Fim")
}
7.3 Código utilizando Semáforo-Mutex

package main say("pensando", p.id)


time.Sleep(time.Second)
import ( }
"fmt" }
"time"
"sync" func say(action string, id int) {
) fmt.Printf("Filosofo #%d is %s\n", id+1, action)
}
var eatWgroup sync.WaitGroup
func main() {
type fork struct{ sync.Mutex }
// How many philosophers and forks
type philosopher struct { count := 5 //n filosofos
id int
leftFork, rightFork *fork // Create forks
} forks := make([]*fork, count)
for i := 0; i < count; i++ {
// Goes from thinking to hungry to eating and done eating then starts forks[i] = new(fork)
over. }
// Adapt the pause values to increased or decrease contentions
// around the forks. // Create philospoher, assign them 2 forks and send them to
func (p philosopher) eat() { the dining table
//defer eatWgroup.Done() philosophers := make([]*philosopher, count)
for j := 0; j < 100000; j++ { for i := 0; i < count; i++ {
p.leftFork.Lock() philosophers[i] = &philosopher{
p.rightFork.Lock() id: i, leftFork: forks[i], rightFork:
forks[(i+1)%count]}
say("comendo", p.id) //eatWgroup.Add(1)
time.Sleep(time.Second) go philosophers[i].eat()
}
p.rightFork.Unlock()
p.leftFork.Unlock() time.Sleep(45 * time.Second)
fmt.Printf("Fim")
say("terminou de comer", p.id) //eatWgroup.Wait()
time.Sleep(time.Second) }

8. Referências

1. PANTUZA, Gustavo. O jantar dos filósofos - problema de sincronização em sistemas operacionais. Blog Pantuza,
2018. Disponível em: https://blog.pantuza.com/artigos/o-jantar-dos-filosofos-prob lema-de-sincronizacao-em-sistemas-operacionais.
Acesso em: 13 de abril de 2022.

2. PERES, Rita. THREADS, SEMÁFOROS E DEADLOCKS – O JANTAR DOS FILÓSOFOS. Revista Programar, 2013.
Disponível em: https://www.revista-programar.info/artigos/threads-semaforos-e-deadlocks-o-jantar-dos-filosofos/. Acesso em 15 de
abril de 2022.

3. BISWAS, Subham. Dining Philosopher Problem Using Semaphores. GeeksforGeeks, 2021. Disponível em:
https://www.geeksforgeeks.org/dining-philosopher-problem-using-semaphores/. Acesso em 15 de abril de 2022.

4. BANCILA, Marius. Dining philosophers in C++11: Chandy-Misra algorithm. Marius Bancila's Blog, 2017. Disponível
em: https://mariusbancila.ro/blog/2017/01/20/dining-philosophers-in-c11-chandy-misra-algorithm/. Acesso em 14 de abril de 2022.

5. Use Go Concurrency to solve Dining Philosopher Problem. ZHEN LI, 2019. Disponível em:
https://zhen404.com/posts/go-concurrency-dining-philosopher/. Acesso em 16 de abril de 2022.

6. DININGPHILOSOPHERS.GO. GitHub, 2018. Disponível em: https://github.com/iokhamafe/Golang/blob/master/


diningphilosophers.go. Acesso em: 15 de abril de 2022.

7. DINER.GO. GitHub, 2015. Disponível em: https://gist.github.com/nmjmdr/d3637b726b564033d318. Acesso em: 15 de abril


de 2022.

Você também pode gostar